istox_gruf 2.7.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 +7 -0
- data/CHANGELOG.md +246 -0
- data/CODE_OF_CONDUCT.md +46 -0
- data/README.md +544 -0
- data/bin/gruf +29 -0
- data/lib/gruf.rb +50 -0
- data/lib/gruf/cli/executor.rb +99 -0
- data/lib/gruf/client.rb +217 -0
- data/lib/gruf/client/error.rb +66 -0
- data/lib/gruf/client/error_factory.rb +105 -0
- data/lib/gruf/configuration.rb +137 -0
- data/lib/gruf/controllers/base.rb +102 -0
- data/lib/gruf/controllers/request.rb +121 -0
- data/lib/gruf/controllers/service_binder.rb +117 -0
- data/lib/gruf/error.rb +230 -0
- data/lib/gruf/errors/debug_info.rb +56 -0
- data/lib/gruf/errors/field.rb +56 -0
- data/lib/gruf/errors/helpers.rb +44 -0
- data/lib/gruf/hooks/base.rb +34 -0
- data/lib/gruf/hooks/executor.rb +47 -0
- data/lib/gruf/hooks/registry.rb +159 -0
- data/lib/gruf/instrumentable_grpc_server.rb +64 -0
- data/lib/gruf/integrations/rails/railtie.rb +10 -0
- data/lib/gruf/interceptors/active_record/connection_reset.rb +48 -0
- data/lib/gruf/interceptors/authentication/basic.rb +87 -0
- data/lib/gruf/interceptors/base.rb +53 -0
- data/lib/gruf/interceptors/client_interceptor.rb +125 -0
- data/lib/gruf/interceptors/context.rb +56 -0
- data/lib/gruf/interceptors/instrumentation/output_metadata_timer.rb +61 -0
- data/lib/gruf/interceptors/instrumentation/request_logging/formatters/base.rb +41 -0
- data/lib/gruf/interceptors/instrumentation/request_logging/formatters/logstash.rb +43 -0
- data/lib/gruf/interceptors/instrumentation/request_logging/formatters/plain.rb +48 -0
- data/lib/gruf/interceptors/instrumentation/request_logging/interceptor.rb +225 -0
- data/lib/gruf/interceptors/instrumentation/statsd.rb +82 -0
- data/lib/gruf/interceptors/registry.rb +161 -0
- data/lib/gruf/interceptors/server_interceptor.rb +34 -0
- data/lib/gruf/interceptors/timer.rb +85 -0
- data/lib/gruf/loggable.rb +30 -0
- data/lib/gruf/logging.rb +53 -0
- data/lib/gruf/outbound/request_context.rb +71 -0
- data/lib/gruf/response.rb +71 -0
- data/lib/gruf/serializers/errors/base.rb +57 -0
- data/lib/gruf/serializers/errors/json.rb +43 -0
- data/lib/gruf/server.rb +294 -0
- data/lib/gruf/synchronized_client.rb +97 -0
- data/lib/gruf/timer.rb +78 -0
- data/lib/gruf/version.rb +20 -0
- metadata +203 -0
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
6
|
+
# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
7
|
+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
8
|
+
# persons to whom the Software is furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
11
|
+
# Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
14
|
+
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
15
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
16
|
+
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
17
|
+
#
|
18
|
+
module Gruf
|
19
|
+
module Errors
|
20
|
+
##
|
21
|
+
# Helper module for standardizing error interaction
|
22
|
+
#
|
23
|
+
module Helpers
|
24
|
+
delegate :add_field_error, :set_debug_info, :has_field_errors?, to: :error
|
25
|
+
|
26
|
+
##
|
27
|
+
# Will issue a GRPC BadStatus exception, with a code based on the code passed.
|
28
|
+
#
|
29
|
+
# @param [Symbol] error_code The network error code that maps to gRPC status codes
|
30
|
+
# @param [Symbol] app_code The application-specific code for the error
|
31
|
+
# @param [String] message (Optional) A detail message about the error
|
32
|
+
# @param [Hash] metadata (Optional) Any metadata to inject into the trailing metadata for the response
|
33
|
+
#
|
34
|
+
def fail!(error_code, app_code = nil, message = '', metadata = {})
|
35
|
+
e = error
|
36
|
+
e.code = error_code.to_sym
|
37
|
+
e.app_code = app_code ? app_code.to_sym : e.code
|
38
|
+
e.message = message.to_s
|
39
|
+
e.metadata = metadata
|
40
|
+
e.fail!(request.active_call)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
6
|
+
# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
7
|
+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
8
|
+
# persons to whom the Software is furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
11
|
+
# Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
14
|
+
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
15
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
16
|
+
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
17
|
+
#
|
18
|
+
module Gruf
|
19
|
+
module Hooks
|
20
|
+
##
|
21
|
+
# Base class for a hook that allows execution at various points of Gruf server processes
|
22
|
+
#
|
23
|
+
class Base
|
24
|
+
include Gruf::Loggable
|
25
|
+
|
26
|
+
##
|
27
|
+
# @param [Hash] options
|
28
|
+
#
|
29
|
+
def initialize(options: nil)
|
30
|
+
@options = options || {}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
6
|
+
# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
7
|
+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
8
|
+
# persons to whom the Software is furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
11
|
+
# Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
14
|
+
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
15
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
16
|
+
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
17
|
+
#
|
18
|
+
module Gruf
|
19
|
+
module Hooks
|
20
|
+
##
|
21
|
+
# Base class for a hook that allows execution at various points of gRPC server processes
|
22
|
+
#
|
23
|
+
class Executor
|
24
|
+
include Gruf::Loggable
|
25
|
+
|
26
|
+
def initialize(hooks: nil)
|
27
|
+
@hooks = hooks || Gruf.hooks&.prepare || []
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# Execute a hook point for each registered hook in the registry
|
32
|
+
#
|
33
|
+
# @param [Symbol] name
|
34
|
+
# @param [Hash] arguments
|
35
|
+
#
|
36
|
+
def call(name, arguments = {})
|
37
|
+
name = name.to_sym
|
38
|
+
|
39
|
+
@hooks.each do |hook|
|
40
|
+
next unless hook.respond_to?(name)
|
41
|
+
|
42
|
+
hook.send(name, arguments)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
6
|
+
# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
7
|
+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
8
|
+
# persons to whom the Software is furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
11
|
+
# Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
14
|
+
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
15
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
16
|
+
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
17
|
+
#
|
18
|
+
module Gruf
|
19
|
+
module Hooks
|
20
|
+
##
|
21
|
+
# Handles registration of hooks
|
22
|
+
#
|
23
|
+
class Registry
|
24
|
+
class HookNotFoundError < StandardError; end
|
25
|
+
|
26
|
+
def initialize
|
27
|
+
@registry = []
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# Add a hook to the registry
|
32
|
+
#
|
33
|
+
# @param [Class] hook_class The class of the hook to add
|
34
|
+
# @param [Hash] options A hash of options to pass into the hook during initialization
|
35
|
+
#
|
36
|
+
def use(hook_class, options = {})
|
37
|
+
hooks_mutex do
|
38
|
+
@registry << {
|
39
|
+
klass: hook_class,
|
40
|
+
options: options
|
41
|
+
}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
##
|
46
|
+
# Remove a hook from the registry
|
47
|
+
#
|
48
|
+
# @param [Class] hook_class The hook class to remove
|
49
|
+
# @raise [HookNotFoundError] if the hook is not found
|
50
|
+
#
|
51
|
+
def remove(hook_class)
|
52
|
+
pos = @registry.find_index { |opts| opts.fetch(:klass, '') == hook_class }
|
53
|
+
raise HookNotFoundError if pos.nil?
|
54
|
+
|
55
|
+
@registry.delete_at(pos)
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# Insert a hook before another specified hook
|
60
|
+
#
|
61
|
+
# @param [Class] before_class The hook to insert before
|
62
|
+
# @param [Class] hook_class The class of the hook to add
|
63
|
+
# @param [Hash] options A hash of options to pass into the hook during initialization
|
64
|
+
# @raise [HookNotFoundError] if the before hook is not found
|
65
|
+
#
|
66
|
+
def insert_before(before_class, hook_class, options = {})
|
67
|
+
hooks_mutex do
|
68
|
+
pos = @registry.find_index { |opts| opts.fetch(:klass, '') == before_class }
|
69
|
+
raise HookNotFoundError if pos.nil?
|
70
|
+
|
71
|
+
@registry.insert(
|
72
|
+
pos,
|
73
|
+
klass: hook_class,
|
74
|
+
options: options
|
75
|
+
)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
##
|
80
|
+
# Insert a hook after another specified hook
|
81
|
+
#
|
82
|
+
# @param [Class] after_class The hook to insert after
|
83
|
+
# @param [Class] hook_class The class of the hook to add
|
84
|
+
# @param [Hash] options A hash of options to pass into the hook during initialization
|
85
|
+
# @raise [HookNotFoundError] if the after hook is not found
|
86
|
+
#
|
87
|
+
def insert_after(after_class, hook_class, options = {})
|
88
|
+
hooks_mutex do
|
89
|
+
pos = @registry.find_index { |opts| opts.fetch(:klass, '') == after_class }
|
90
|
+
raise HookNotFoundError if pos.nil?
|
91
|
+
|
92
|
+
@registry.insert(
|
93
|
+
(pos + 1),
|
94
|
+
klass: hook_class,
|
95
|
+
options: options
|
96
|
+
)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
##
|
101
|
+
# Return a list of the hook classes in the registry in their execution order
|
102
|
+
#
|
103
|
+
# @return [Array<Class>]
|
104
|
+
#
|
105
|
+
def list
|
106
|
+
hooks_mutex do
|
107
|
+
@registry.map { |h| h[:klass] }
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
##
|
112
|
+
# Lazily load and return all hooks for the given request
|
113
|
+
#
|
114
|
+
# @return [Array<Gruf::Hooks::Base>]
|
115
|
+
#
|
116
|
+
def prepare
|
117
|
+
is = []
|
118
|
+
hooks_mutex do
|
119
|
+
@registry.each do |o|
|
120
|
+
is << o[:klass].new(options: o[:options])
|
121
|
+
end
|
122
|
+
end
|
123
|
+
is
|
124
|
+
end
|
125
|
+
|
126
|
+
##
|
127
|
+
# Clear the registry
|
128
|
+
#
|
129
|
+
def clear
|
130
|
+
hooks_mutex do
|
131
|
+
@registry = []
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
##
|
136
|
+
# @return [Integer] The number of hooks currently loaded
|
137
|
+
#
|
138
|
+
def count
|
139
|
+
hooks_mutex do
|
140
|
+
@registry ||= []
|
141
|
+
@registry.count
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
private
|
146
|
+
|
147
|
+
##
|
148
|
+
# Handle mutations to the hook registry in a thread-safe manner
|
149
|
+
#
|
150
|
+
def hooks_mutex(&block)
|
151
|
+
@hooks_mutex ||= begin
|
152
|
+
require 'monitor'
|
153
|
+
Monitor.new
|
154
|
+
end
|
155
|
+
@hooks_mutex.synchronize(&block)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Gruf
|
4
|
+
##
|
5
|
+
# A subclass of GRPC::RpcServer that can be used for enhanced monitoring
|
6
|
+
# of thread pool. Note that since we are reaching into the internals of
|
7
|
+
# GRPC::RpcServer, we need to watch the evolution of that class.
|
8
|
+
#
|
9
|
+
class InstrumentableGrpcServer < GRPC::RpcServer
|
10
|
+
##
|
11
|
+
# Add an event_listener_proc that, if supplied, will be called
|
12
|
+
# when interesting events happen in the server.
|
13
|
+
#
|
14
|
+
def initialize(pool_size: DEFAULT_POOL_SIZE,
|
15
|
+
max_waiting_requests: DEFAULT_MAX_WAITING_REQUESTS,
|
16
|
+
poll_period: DEFAULT_POLL_PERIOD,
|
17
|
+
pool_keep_alive: Pool::DEFAULT_KEEP_ALIVE,
|
18
|
+
connect_md_proc: nil,
|
19
|
+
server_args: {},
|
20
|
+
interceptors: [],
|
21
|
+
event_listener_proc: nil)
|
22
|
+
# Call the base class initializer
|
23
|
+
super(
|
24
|
+
pool_size: pool_size,
|
25
|
+
max_waiting_requests: max_waiting_requests,
|
26
|
+
poll_period: poll_period,
|
27
|
+
pool_keep_alive: pool_keep_alive,
|
28
|
+
connect_md_proc: connect_md_proc,
|
29
|
+
server_args: server_args,
|
30
|
+
interceptors: interceptors
|
31
|
+
)
|
32
|
+
|
33
|
+
# Save event listener for later
|
34
|
+
@event_listener_proc = event_listener_proc
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Notify the event listener of something interesting
|
39
|
+
#
|
40
|
+
def notify(event)
|
41
|
+
return if @event_listener_proc.nil? || !@event_listener_proc.respond_to?(:call)
|
42
|
+
|
43
|
+
@event_listener_proc.call(event)
|
44
|
+
end
|
45
|
+
|
46
|
+
##
|
47
|
+
# Hook into the thread pool availability check for monitoring
|
48
|
+
#
|
49
|
+
def available?(an_rpc)
|
50
|
+
super.tap do |obj|
|
51
|
+
notify(:thread_pool_exhausted) unless obj
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# Hook into the method implementation check for monitoring
|
57
|
+
#
|
58
|
+
def implemented?(an_rpc)
|
59
|
+
super.tap do |obj|
|
60
|
+
notify(:unimplemented) unless obj
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
6
|
+
# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
7
|
+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
8
|
+
# persons to whom the Software is furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
11
|
+
# Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
14
|
+
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
15
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
16
|
+
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
17
|
+
#
|
18
|
+
module Gruf
|
19
|
+
module Interceptors
|
20
|
+
module ActiveRecord
|
21
|
+
##
|
22
|
+
# Resets the ActiveRecord connection to maintain accurate connected state in the thread pool
|
23
|
+
#
|
24
|
+
class ConnectionReset < ::Gruf::Interceptors::ServerInterceptor
|
25
|
+
##
|
26
|
+
# Reset any ActiveRecord connections after a gRPC service is called. Because of the way gRPC manages its
|
27
|
+
# connection pool, we need to ensure that this is done to properly
|
28
|
+
#
|
29
|
+
def call
|
30
|
+
::ActiveRecord::Base.establish_connection if enabled? && !::ActiveRecord::Base.connection.active?
|
31
|
+
|
32
|
+
yield
|
33
|
+
ensure
|
34
|
+
::ActiveRecord::Base.clear_active_connections! if enabled?
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
##
|
40
|
+
# @return [Boolean] If AR is loaded, we can enable this hook safely
|
41
|
+
#
|
42
|
+
def enabled?
|
43
|
+
defined?(::ActiveRecord::Base)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
6
|
+
# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
7
|
+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
8
|
+
# persons to whom the Software is furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
11
|
+
# Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
14
|
+
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
15
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
16
|
+
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
17
|
+
#
|
18
|
+
require 'base64'
|
19
|
+
|
20
|
+
module Gruf
|
21
|
+
module Interceptors
|
22
|
+
module Authentication
|
23
|
+
##
|
24
|
+
# Handles basic authentication for gRPC requests
|
25
|
+
#
|
26
|
+
class Basic < Gruf::Interceptors::ServerInterceptor
|
27
|
+
##
|
28
|
+
# Validate authentication
|
29
|
+
#
|
30
|
+
def call
|
31
|
+
fail!(:unauthenticated, :unauthenticated) unless bypass? || valid?
|
32
|
+
yield
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
##
|
38
|
+
# @return [Boolean] If this method is in the excluded list, bypass
|
39
|
+
#
|
40
|
+
def bypass?
|
41
|
+
options.fetch(:excluded_methods, []).include?(request.method_name)
|
42
|
+
end
|
43
|
+
|
44
|
+
##
|
45
|
+
# @return [Boolean] True if the basic authentication was valid
|
46
|
+
#
|
47
|
+
def valid?
|
48
|
+
server_credentials.any? do |cred|
|
49
|
+
username = cred.fetch(:username, '').to_s
|
50
|
+
password = cred.fetch(:password, '').to_s
|
51
|
+
if username.empty?
|
52
|
+
request_password == password
|
53
|
+
else
|
54
|
+
request_credentials == "#{username}:#{password}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
##
|
60
|
+
# @return [Array<Hash>] An array of valid server credentials for this service
|
61
|
+
#
|
62
|
+
def server_credentials
|
63
|
+
options.fetch(:credentials, [])
|
64
|
+
end
|
65
|
+
|
66
|
+
##
|
67
|
+
# @return [String] The decoded request credentials
|
68
|
+
#
|
69
|
+
def request_credentials
|
70
|
+
credentials = if request.active_call.respond_to?(:metadata)
|
71
|
+
request.active_call.metadata['authorization'].to_s
|
72
|
+
else
|
73
|
+
''
|
74
|
+
end
|
75
|
+
Base64.decode64(credentials.to_s.gsub('Basic ', '').strip)
|
76
|
+
end
|
77
|
+
|
78
|
+
##
|
79
|
+
# @return [String] The decoded request password
|
80
|
+
#
|
81
|
+
def request_password
|
82
|
+
@request_password ||= request_credentials.split(':').last
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|