rails-auth 1.3.0 → 2.0.1
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.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/CHANGES.md +30 -0
- data/README.md +27 -485
- data/lib/rails/auth.rb +3 -0
- data/lib/rails/auth/acl.rb +20 -15
- data/lib/rails/auth/acl/matchers/allow_all.rb +1 -1
- data/lib/rails/auth/acl/middleware.rb +6 -1
- data/lib/rails/auth/acl/resource.rb +12 -11
- data/lib/rails/auth/config_builder.rb +83 -0
- data/lib/rails/auth/credentials.rb +17 -32
- data/lib/rails/auth/credentials/injector_middleware.rb +2 -2
- data/lib/rails/auth/env.rb +64 -0
- data/lib/rails/auth/error_page/debug_page.html.erb +2 -2
- data/lib/rails/auth/exceptions.rb +8 -2
- data/lib/rails/auth/helpers.rb +64 -0
- data/lib/rails/auth/monitor/middleware.rb +28 -0
- data/lib/rails/auth/rack.rb +4 -2
- data/lib/rails/auth/rspec/helper_methods.rb +20 -2
- data/lib/rails/auth/version.rb +1 -1
- data/lib/rails/auth/x509/matcher.rb +1 -1
- data/spec/rails/auth/acl/middleware_spec.rb +2 -1
- data/spec/rails/auth/acl/resource_spec.rb +17 -17
- data/spec/rails/auth/acl_spec.rb +3 -3
- data/spec/rails/auth/credentials/injector_middleware_spec.rb +1 -1
- data/spec/rails/auth/credentials_spec.rb +20 -41
- data/spec/rails/auth/env_spec.rb +31 -0
- data/spec/rails/auth/monitor/middleware_spec.rb +39 -0
- data/spec/rails/auth/rspec/helper_methods_spec.rb +26 -0
- data/spec/rails/auth/rspec/matchers/acl_matchers_spec.rb +2 -2
- data/spec/rails/auth/x509/matcher_spec.rb +1 -1
- metadata +8 -3
- data/lib/rails/auth/override.rb +0 -29
data/lib/rails/auth.rb
CHANGED
data/lib/rails/auth/acl.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Pull in default
|
1
|
+
# Pull in default matchers
|
2
2
|
require "rails/auth/acl/matchers/allow_all"
|
3
3
|
|
4
4
|
module Rails
|
@@ -7,7 +7,7 @@ module Rails
|
|
7
7
|
class ACL
|
8
8
|
attr_reader :resources
|
9
9
|
|
10
|
-
#
|
10
|
+
# Matchers available by default in ACLs
|
11
11
|
DEFAULT_MATCHERS = {
|
12
12
|
allow_all: Matchers::AllowAll
|
13
13
|
}.freeze
|
@@ -21,7 +21,7 @@ module Rails
|
|
21
21
|
end
|
22
22
|
|
23
23
|
# @param [Array<Hash>] :acl Access Control List configuration
|
24
|
-
# @param [Hash] :matchers
|
24
|
+
# @param [Hash] :matchers authorizers use with this ACL
|
25
25
|
#
|
26
26
|
def initialize(acl, matchers: {})
|
27
27
|
raise TypeError, "expected Array for acl, got #{acl.class}" unless acl.is_a?(Array)
|
@@ -34,32 +34,37 @@ module Rails
|
|
34
34
|
resources = entry["resources"]
|
35
35
|
raise ParseError, "no 'resources' key present in entry: #{entry.inspect}" unless resources
|
36
36
|
|
37
|
-
|
37
|
+
matcher_instances = parse_matchers(entry, matchers.merge(DEFAULT_MATCHERS))
|
38
38
|
|
39
39
|
resources.each do |resource|
|
40
|
-
@resources << Resource.new(resource,
|
40
|
+
@resources << Resource.new(resource, matcher_instances).freeze
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
44
|
@resources.freeze
|
45
45
|
end
|
46
46
|
|
47
|
-
# Match the Rack environment against the ACL, checking all
|
47
|
+
# Match the Rack environment against the ACL, checking all matchers
|
48
48
|
#
|
49
49
|
# @param [Hash] :env Rack environment
|
50
50
|
#
|
51
|
-
# @return [
|
51
|
+
# @return [String, nil] name of the first matching matcher, or nil if unauthorized
|
52
52
|
#
|
53
53
|
def match(env)
|
54
|
-
@resources.
|
54
|
+
@resources.each do |resource|
|
55
|
+
matcher_name = resource.match(env)
|
56
|
+
return matcher_name if matcher_name
|
57
|
+
end
|
58
|
+
|
59
|
+
nil
|
55
60
|
end
|
56
61
|
|
57
|
-
# Find all resources that match the ACL.
|
62
|
+
# Find all resources that match the ACL. Matchers are *NOT* checked,
|
58
63
|
# instead only the initial checks for the "resources" section of the ACL
|
59
|
-
# are performed. Use the `#match` method to validate
|
64
|
+
# are performed. Use the `#match` method to validate matchers.
|
60
65
|
#
|
61
66
|
# This method is intended for debugging AuthZ failures. It can find all
|
62
|
-
# resources that match the given request so the corresponding
|
67
|
+
# resources that match the given request so the corresponding matchers
|
63
68
|
# can be introspected.
|
64
69
|
#
|
65
70
|
# @param [Hash] :env Rack environment
|
@@ -72,8 +77,8 @@ module Rails
|
|
72
77
|
|
73
78
|
private
|
74
79
|
|
75
|
-
def
|
76
|
-
|
80
|
+
def parse_matchers(entry, matchers)
|
81
|
+
matcher_instances = {}
|
77
82
|
|
78
83
|
entry.each do |name, options|
|
79
84
|
next if name == "resources"
|
@@ -82,10 +87,10 @@ module Rails
|
|
82
87
|
raise ArgumentError, "no matcher for #{name}" unless matcher_class
|
83
88
|
raise TypeError, "expected Class for #{name}" unless matcher_class.is_a?(Class)
|
84
89
|
|
85
|
-
|
90
|
+
matcher_instances[name.freeze] = matcher_class.new(options.freeze).freeze
|
86
91
|
end
|
87
92
|
|
88
|
-
|
93
|
+
matcher_instances.freeze
|
89
94
|
end
|
90
95
|
end
|
91
96
|
end
|
@@ -22,7 +22,12 @@ module Rails
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def call(env)
|
25
|
-
|
25
|
+
unless Rails::Auth.authorized?(env)
|
26
|
+
matcher_name = @acl.match(env)
|
27
|
+
raise NotAuthorizedError, "unauthorized request" unless matcher_name
|
28
|
+
Rails::Auth.set_allowed_by(env, "matcher:#{matcher_name}")
|
29
|
+
end
|
30
|
+
|
26
31
|
@app.call(env)
|
27
32
|
end
|
28
33
|
end
|
@@ -5,7 +5,7 @@ module Rails
|
|
5
5
|
class ACL
|
6
6
|
# Rules for a particular route
|
7
7
|
class Resource
|
8
|
-
attr_reader :http_methods, :path, :host, :
|
8
|
+
attr_reader :http_methods, :path, :host, :matchers
|
9
9
|
|
10
10
|
# Valid HTTP methods
|
11
11
|
HTTP_METHODS = %w(GET HEAD PUT POST DELETE OPTIONS PATCH LINK UNLINK).freeze
|
@@ -15,11 +15,11 @@ module Rails
|
|
15
15
|
|
16
16
|
# @option :options [String] :method HTTP method allowed ("ALL" for all methods)
|
17
17
|
# @option :options [String] :path path to the resource (regex syntax allowed)
|
18
|
-
# @param [Hash] :
|
18
|
+
# @param [Hash] :matchers which matchers are used for this resource
|
19
19
|
#
|
20
|
-
def initialize(options,
|
21
|
-
raise TypeError, "expected Hash for options"
|
22
|
-
raise TypeError, "expected Hash for
|
20
|
+
def initialize(options, matchers)
|
21
|
+
raise TypeError, "expected Hash for options" unless options.is_a?(Hash)
|
22
|
+
raise TypeError, "expected Hash for matchers" unless matchers.is_a?(Hash)
|
23
23
|
|
24
24
|
unless (extra_keys = options.keys - VALID_OPTIONS).empty?
|
25
25
|
raise ParseError, "unrecognized key in ACL resource: #{extra_keys.first}"
|
@@ -30,7 +30,7 @@ module Rails
|
|
30
30
|
|
31
31
|
@http_methods = extract_methods(methods)
|
32
32
|
@path = /\A#{path}\z/
|
33
|
-
@
|
33
|
+
@matchers = matchers.freeze
|
34
34
|
|
35
35
|
# Unlike method and path, host is optional
|
36
36
|
host = options["host"]
|
@@ -38,19 +38,20 @@ module Rails
|
|
38
38
|
end
|
39
39
|
|
40
40
|
# Match this resource against the given Rack environment, checking all
|
41
|
-
#
|
41
|
+
# matchers to ensure at least one of them matches
|
42
42
|
#
|
43
43
|
# @param [Hash] :env Rack environment
|
44
44
|
#
|
45
|
-
# @return [
|
45
|
+
# @return [String, nil] name of the matcher which matched, or nil if none matched
|
46
46
|
#
|
47
47
|
def match(env)
|
48
|
-
return
|
49
|
-
@
|
48
|
+
return nil unless match!(env)
|
49
|
+
name, = @matchers.find { |_name, matcher| matcher.match(env) }
|
50
|
+
name
|
50
51
|
end
|
51
52
|
|
52
53
|
# Match *only* the request method/path/host against the given Rack environment.
|
53
|
-
#
|
54
|
+
# matchers are NOT checked.
|
54
55
|
#
|
55
56
|
# @param [Hash] :env Rack environment
|
56
57
|
#
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Rails
|
2
|
+
module Auth
|
3
|
+
# Configures Rails::Auth middleware for use in a Rails application
|
4
|
+
module ConfigBuilder
|
5
|
+
extend self
|
6
|
+
|
7
|
+
# Application-level configuration (i.e. config/application.rb)
|
8
|
+
def application(config, acl_file: Rails.root.join("config/acl.yml"), matchers: {})
|
9
|
+
config.x.rails_auth.acl = Rails::Auth::ACL.from_yaml(
|
10
|
+
File.read(acl_file.to_s),
|
11
|
+
matchers: matchers
|
12
|
+
)
|
13
|
+
|
14
|
+
config.middleware.use Rails::Auth::ACL::Middleware, acl: config.x.acl
|
15
|
+
end
|
16
|
+
|
17
|
+
# Development configuration (i.e. config/environments/development.rb)
|
18
|
+
def development(config, development_credentials: {}, error_page: :debug)
|
19
|
+
error_page_middleware(config, error_page)
|
20
|
+
credential_injector_middleware(config, development_credentials) unless development_credentials.empty?
|
21
|
+
end
|
22
|
+
|
23
|
+
# Test configuration (i.e. config/environments/test.rb)
|
24
|
+
def test(config)
|
25
|
+
# Simulated credentials to be injected with InjectorMiddleware
|
26
|
+
credential_injector_middleware(config, config.x.rails_auth.test_credentials ||= {})
|
27
|
+
end
|
28
|
+
|
29
|
+
def production(
|
30
|
+
config,
|
31
|
+
cert_filters: nil,
|
32
|
+
require_cert: false,
|
33
|
+
ca_file: nil,
|
34
|
+
error_page: Rails.root.join("public/403.html"),
|
35
|
+
monitor: nil
|
36
|
+
)
|
37
|
+
raise ArgumentError, "no cert_filters given but require_cert is true" if require_cert && !cert_filters
|
38
|
+
raise ArgumentError, "no ca_file given but cert_filters were set" if cert_filters && !ca_file
|
39
|
+
|
40
|
+
error_page_middleware(config, error_page)
|
41
|
+
|
42
|
+
if cert_filters
|
43
|
+
config.middleware.insert_before Rails::Auth::ACL::Middleware,
|
44
|
+
Rails::Auth::X509::Middleware,
|
45
|
+
require_cert: require_cert,
|
46
|
+
cert_filters: cert_filters,
|
47
|
+
ca_file: ca_file,
|
48
|
+
logger: Rails.logger
|
49
|
+
end
|
50
|
+
|
51
|
+
return unless monitor
|
52
|
+
config.middleware.insert_before Rails::Auth::ACL::Middleware,
|
53
|
+
Rails::Auth::Monitor::Middleware,
|
54
|
+
monitor
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
# Adds error page middleware to the chain
|
60
|
+
def error_page_middleware(config, error_page)
|
61
|
+
case error_page
|
62
|
+
when :debug
|
63
|
+
config.middleware.insert_before Rails::Auth::ACL::Middleware,
|
64
|
+
Rails::Auth::ErrorPage::DebugMiddleware,
|
65
|
+
acl: config.x.rails_auth.acl
|
66
|
+
when Pathname, String
|
67
|
+
config.middleware.insert_before Rails::Auth::ACL::Middleware,
|
68
|
+
Rails::Auth::ErrorPage::Middleware,
|
69
|
+
page_body: Pathname(error_page).read
|
70
|
+
when FalseClass, NilClass
|
71
|
+
else raise TypeError, "bad error page mode: #{mode.inspect}"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Adds Rails::Auth::Credentials::InjectorMiddleware to the chain with the given credentials
|
76
|
+
def credential_injector_middleware(config, credentials)
|
77
|
+
config.middleware.insert_before Rails::Auth::ACL::Middleware,
|
78
|
+
Rails::Auth::Credentials::InjectorMiddleware,
|
79
|
+
credentials
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -1,45 +1,30 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "forwardable"
|
4
|
+
|
3
5
|
module Rails
|
4
6
|
# Modular resource-based authentication and authorization for Rails/Rack
|
5
7
|
module Auth
|
6
|
-
#
|
7
|
-
|
8
|
-
|
9
|
-
# Functionality for storing credentials in the Rack environment
|
10
|
-
module Credentials
|
11
|
-
# Obtain credentials from a Rack environment
|
12
|
-
#
|
13
|
-
# @param [Hash] :env Rack environment
|
14
|
-
#
|
15
|
-
def credentials(env)
|
16
|
-
env.fetch(CREDENTIALS_ENV_KEY, {})
|
17
|
-
end
|
18
|
-
|
19
|
-
# Add a credential to the Rack environment
|
20
|
-
#
|
21
|
-
# @param [Hash] :env Rack environment
|
22
|
-
# @param [String] :type credential type to add to the environment
|
23
|
-
# @param [Object] :credential object to add to the environment
|
24
|
-
#
|
25
|
-
def add_credential(env, type, credential)
|
26
|
-
credentials = env[CREDENTIALS_ENV_KEY] ||= {}
|
8
|
+
# Stores a set of credentials
|
9
|
+
class Credentials
|
10
|
+
extend Forwardable
|
27
11
|
|
28
|
-
|
29
|
-
# the same credential should be harmless
|
30
|
-
return env if credentials.key?(type) && credentials[type] == credential
|
12
|
+
def_delegators :@credentials, :[], :fetch, :empty?, :key?, :to_hash
|
31
13
|
|
32
|
-
|
33
|
-
|
34
|
-
|
14
|
+
def self.from_rack_env(env)
|
15
|
+
new(env.fetch(Rails::Auth::Env::CREDENTIALS_ENV_KEY, {}))
|
16
|
+
end
|
35
17
|
|
36
|
-
|
18
|
+
def initialize(credentials = {})
|
19
|
+
raise TypeError, "expected Hash, got #{credentials.class}" unless credentials.is_a?(Hash)
|
20
|
+
@credentials = credentials
|
21
|
+
end
|
37
22
|
|
38
|
-
|
23
|
+
def []=(type, value)
|
24
|
+
raise TypeError, "expected String for type, got #{type.class}" unless type.is_a?(String)
|
25
|
+
raise AlreadyAuthorizedError, "credential '#{type}' has already been set" if @credentials.key?(type)
|
26
|
+
@credentials[type] = value
|
39
27
|
end
|
40
28
|
end
|
41
|
-
|
42
|
-
# Include these functions in Rails::Auth for convenience
|
43
|
-
extend Credentials
|
44
29
|
end
|
45
30
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Rails
|
2
2
|
module Auth
|
3
|
-
|
3
|
+
class Credentials
|
4
4
|
# A middleware for injecting an arbitrary credentials hash into the Rack environment
|
5
5
|
# This is intended for development and testing purposes where you would like to
|
6
6
|
# simulate a given X.509 certificate being used in a request or user logged in
|
@@ -11,7 +11,7 @@ module Rails
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def call(env)
|
14
|
-
env[Rails::Auth::CREDENTIALS_ENV_KEY] = @credentials
|
14
|
+
env[Rails::Auth::Env::CREDENTIALS_ENV_KEY] = @credentials
|
15
15
|
@app.call(env)
|
16
16
|
end
|
17
17
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rails
|
4
|
+
module Auth
|
5
|
+
# Wrapper for Rack environments with Rails::Auth helpers
|
6
|
+
class Env
|
7
|
+
# Rack environment key for marking external authorization
|
8
|
+
AUTHORIZED_ENV_KEY = "rails-auth.authorized".freeze
|
9
|
+
|
10
|
+
# Rack environment key for storing what allowed the request
|
11
|
+
ALLOWED_BY_ENV_KEY = "rails-auth.allowed-by".freeze
|
12
|
+
|
13
|
+
# Rack environment key for all rails-auth credentials
|
14
|
+
CREDENTIALS_ENV_KEY = "rails-auth.credentials".freeze
|
15
|
+
|
16
|
+
attr_reader :allowed_by, :credentials
|
17
|
+
|
18
|
+
# @param [Hash] :env Rack environment
|
19
|
+
def initialize(env, credentials: {}, authorized: false, allowed_by: nil)
|
20
|
+
raise TypeError, "expected Hash for credentials, got #{credentials.class}" unless credentials.is_a?(Hash)
|
21
|
+
|
22
|
+
@env = env
|
23
|
+
@credentials = Credentials.new(credentials.merge(@env.fetch(CREDENTIALS_ENV_KEY, {})))
|
24
|
+
@authorized = env.fetch(AUTHORIZED_ENV_KEY, authorized)
|
25
|
+
@allowed_by = env.fetch(ALLOWED_BY_ENV_KEY, allowed_by)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Check whether a request has been authorized
|
29
|
+
def authorized?
|
30
|
+
@authorized
|
31
|
+
end
|
32
|
+
|
33
|
+
# Mark the environment as authorized to access the requested resource
|
34
|
+
#
|
35
|
+
# @param [String] :allowed_by label of what allowed the request
|
36
|
+
def authorize(allowed_by)
|
37
|
+
self.allowed_by = allowed_by
|
38
|
+
@authorized = true
|
39
|
+
end
|
40
|
+
|
41
|
+
# Set the name of the authority which authorized the request
|
42
|
+
#
|
43
|
+
# @param [String] :allowed_by label of what allowed the request
|
44
|
+
def allowed_by=(allowed_by)
|
45
|
+
raise AlreadyAuthorizedError, "already allowed by #{@allowed_by.ispect}" if @allowed_by
|
46
|
+
raise TypeError, "expected String for allowed_by, got #{allowed_by.class}" unless allowed_by.is_a?(String)
|
47
|
+
@allowed_by = allowed_by
|
48
|
+
end
|
49
|
+
|
50
|
+
# Return a Rack environment
|
51
|
+
#
|
52
|
+
# @return [Hash] Rack environment
|
53
|
+
def to_rack
|
54
|
+
credentials = @env[CREDENTIALS_ENV_KEY] ||= {}
|
55
|
+
credentials.merge!(@credentials.to_hash)
|
56
|
+
|
57
|
+
@env[AUTHORIZED_ENV_KEY] = @authorized if @authorized
|
58
|
+
@env[ALLOWED_BY_ENV_KEY] = @allowed_by if @allowed_by
|
59
|
+
|
60
|
+
@env
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -113,8 +113,8 @@
|
|
113
113
|
<td class="label"><%= h((resource.http_methods || "ALL").join(" ")) %> <%= h(format_path(resource.path)) %></td>
|
114
114
|
<td>
|
115
115
|
<ul>
|
116
|
-
<% resource.
|
117
|
-
<li><%= h(name) %>: <%= h(format_attributes(
|
116
|
+
<% resource.matchers.each do |name, matcher| %>
|
117
|
+
<li><%= h(name) %>: <%= h(format_attributes(matcher)) %></li>
|
118
118
|
<% end %>
|
119
119
|
</ul>
|
120
120
|
</td>
|
@@ -1,9 +1,15 @@
|
|
1
1
|
module Rails
|
2
2
|
module Auth
|
3
|
+
# Base class of all Rails::Auth errors
|
4
|
+
Error = Class.new(StandardError)
|
5
|
+
|
3
6
|
# Unauthorized!
|
4
|
-
NotAuthorizedError = Class.new(
|
7
|
+
NotAuthorizedError = Class.new(Error)
|
5
8
|
|
6
9
|
# Error parsing e.g. an ACL
|
7
|
-
ParseError = Class.new(
|
10
|
+
ParseError = Class.new(Error)
|
11
|
+
|
12
|
+
# Internal errors involving authorizing things that are already authorized
|
13
|
+
AlreadyAuthorizedError = Class.new(Error)
|
8
14
|
end
|
9
15
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Rails
|
2
|
+
# Modular resource-based authentication and authorization for Rails/Rack
|
3
|
+
module Auth
|
4
|
+
module_function
|
5
|
+
|
6
|
+
# Mark a request as externally authorized. Causes ACL checks to be skipped.
|
7
|
+
#
|
8
|
+
# @param [Hash] :rack_env Rack environment
|
9
|
+
# @param [String] :allowed_by what allowed the request
|
10
|
+
#
|
11
|
+
def authorized!(rack_env, allowed_by)
|
12
|
+
Env.new(rack_env).tap do |env|
|
13
|
+
env.authorize(allowed_by)
|
14
|
+
end.to_rack
|
15
|
+
end
|
16
|
+
|
17
|
+
# Check whether a request has been authorized
|
18
|
+
#
|
19
|
+
# @param [Hash] :rack_env Rack environment
|
20
|
+
#
|
21
|
+
def authorized?(rack_env)
|
22
|
+
Env.new(rack_env).authorized?
|
23
|
+
end
|
24
|
+
|
25
|
+
# Mark what authorized the request in the Rack environment
|
26
|
+
#
|
27
|
+
# @param [Hash] :env Rack environment
|
28
|
+
# @param [String] :allowed_by what allowed this request
|
29
|
+
def set_allowed_by(rack_env, allowed_by)
|
30
|
+
Env.new(rack_env).tap do |env|
|
31
|
+
env.allowed_by = allowed_by
|
32
|
+
end.to_rack
|
33
|
+
end
|
34
|
+
|
35
|
+
# Read what authorized the request
|
36
|
+
#
|
37
|
+
# @param [Hash] :rack_env Rack environment
|
38
|
+
#
|
39
|
+
# @return [String, nil] what authorized the request
|
40
|
+
def allowed_by(rack_env)
|
41
|
+
Env.new(rack_env).allowed_by
|
42
|
+
end
|
43
|
+
|
44
|
+
# Obtain credentials from a Rack environment
|
45
|
+
#
|
46
|
+
# @param [Hash] :rack_env Rack environment
|
47
|
+
#
|
48
|
+
def credentials(rack_env)
|
49
|
+
Credentials.from_rack_env(rack_env)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Add a credential to the Rack environment
|
53
|
+
#
|
54
|
+
# @param [Hash] :rack_env Rack environment
|
55
|
+
# @param [String] :type credential type to add to the environment
|
56
|
+
# @param [Object] :credential object to add to the environment
|
57
|
+
#
|
58
|
+
def add_credential(rack_env, type, credential)
|
59
|
+
Env.new(rack_env).tap do |env|
|
60
|
+
env.credentials[type] = credential
|
61
|
+
end.to_rack
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|