rails-auth 1.3.0 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|