unrestful 0.1.3 → 0.1.4
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/lib/unrestful/async_job.rb +107 -107
- data/lib/unrestful/errors.rb +14 -7
- data/lib/unrestful/fail_response.rb +20 -20
- data/lib/unrestful/json_web_token.rb +26 -26
- data/lib/unrestful/jwt_secured.rb +38 -28
- data/lib/unrestful/response.rb +2 -2
- data/lib/unrestful/rpc_controller.rb +30 -30
- data/lib/unrestful/success_response.rb +2 -2
- data/lib/unrestful/utils.rb +16 -16
- data/lib/unrestful/version.rb +1 -1
- data/lib/unrestful.rb +39 -39
- metadata +3 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8ed14da4951593e6b5461d2d776a16097b46ed7495ab60ca7cae9c24fc778bdc
|
4
|
+
data.tar.gz: 2f9dd412dcae54e6a86fff857752898d1f654f2d6acf7cf5f1d7e6d8f6c87bf1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f58bde1f04744005aeec13d1e46c1002c66de852086b1da36843d66fe501320339f1d8e63e2c5c6380e9943b0abaa13f16a8035a6e9f7d5a0ee55e1622e05f33
|
7
|
+
data.tar.gz: d816f606a7aa1e36857c046e76df687d7866d4df5b35085f58642ac7ad9a39b430f737ea1dc6f16ecee0c599acfb85bb4e1a3191b4a131f5f139f1f48ac5fffe
|
data/lib/unrestful/async_job.rb
CHANGED
@@ -1,111 +1,111 @@
|
|
1
1
|
require 'redis'
|
2
2
|
|
3
3
|
module Unrestful
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
4
|
+
class AsyncJob
|
5
|
+
include ActiveModel::Serializers::JSON
|
6
|
+
|
7
|
+
ALLOCATED = 0
|
8
|
+
RUNNING = 1
|
9
|
+
FAILED = 2
|
10
|
+
SUCCESS = 3
|
11
|
+
|
12
|
+
KEY_TIMEOUT = 3600
|
13
|
+
KEY_LENGTH = 10
|
14
|
+
CHANNEL_TIMEOUT = 10
|
15
|
+
|
16
|
+
attr_reader :job_id
|
17
|
+
|
18
|
+
def attributes
|
19
|
+
{
|
20
|
+
job_id: job_id,
|
21
|
+
state: state,
|
22
|
+
last_message: last_message,
|
23
|
+
ttl: ttl
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize(job_id: nil)
|
28
|
+
if job_id.nil?
|
29
|
+
@job_id = SecureRandom.hex(KEY_LENGTH)
|
30
|
+
else
|
31
|
+
@job_id = job_id
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def update(state, message: '')
|
36
|
+
raise ArgumentError, 'failed states must have a message' if message.blank? && state == FAILED
|
37
|
+
|
38
|
+
redis.set(job_key, state)
|
39
|
+
redis.set(job_message, message) unless message.blank?
|
40
|
+
|
41
|
+
if state == ALLOCATED
|
42
|
+
redis.expire(job_key, KEY_TIMEOUT)
|
43
|
+
redis.expire(job_message, KEY_TIMEOUT)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def ttl
|
48
|
+
redis.ttl(job_key)
|
49
|
+
end
|
50
|
+
|
51
|
+
def state
|
52
|
+
redis.get(job_key)
|
53
|
+
end
|
54
|
+
|
55
|
+
def last_message
|
56
|
+
redis.get(job_message)
|
57
|
+
end
|
58
|
+
|
59
|
+
def delete
|
60
|
+
redis.del(job_key)
|
61
|
+
redis.del(job_message)
|
62
|
+
end
|
63
|
+
|
64
|
+
def subscribe(timeout: CHANNEL_TIMEOUT, &block)
|
65
|
+
raise AsyncError, "job #{job_key} doesn't exist" unless valid?
|
66
|
+
|
67
|
+
redis.subscribe_with_timeout(timeout, job_channel, &block)
|
68
|
+
end
|
69
|
+
|
70
|
+
def publish(message)
|
71
|
+
raise AsyncError, "job #{job_key} doesn't exist" unless valid?
|
72
|
+
|
73
|
+
redis.publish(job_channel, message)
|
74
|
+
end
|
75
|
+
|
76
|
+
def valid?
|
77
|
+
redis.exists(job_key)
|
78
|
+
end
|
79
|
+
|
80
|
+
def unsubscribe
|
81
|
+
redis.unsubscribe(job_channel)
|
82
|
+
rescue
|
83
|
+
# ignore unsub errors
|
84
|
+
end
|
85
|
+
|
86
|
+
def redis
|
87
|
+
@redis ||= Redis.new(url: Unrestful.configuration.redis_address)
|
88
|
+
end
|
89
|
+
|
90
|
+
def close
|
91
|
+
redis.unsubscribe(job_channel) if redis.subscribed?
|
92
|
+
ensure
|
93
|
+
@redis.quit
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def job_key
|
99
|
+
"unrestful:job:state:#{@job_id}"
|
100
|
+
end
|
101
|
+
|
102
|
+
def job_channel
|
103
|
+
"unrestful:job:channel:#{@job_id}"
|
104
|
+
end
|
105
|
+
|
106
|
+
def job_message
|
107
|
+
"unrestful:job:message:#{@job_id}"
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
111
|
end
|
data/lib/unrestful/errors.rb
CHANGED
@@ -1,9 +1,16 @@
|
|
1
1
|
module Unrestful
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
2
|
+
class Error < StandardError;
|
3
|
+
end
|
4
|
+
class FailError < Error;
|
5
|
+
end
|
6
|
+
class AuthError < Error;
|
7
|
+
end
|
8
|
+
class NotLiveError < Error;
|
9
|
+
end
|
10
|
+
class LiveError < Error;
|
11
|
+
end
|
12
|
+
class AsyncError < Error;
|
13
|
+
end
|
14
|
+
class StreamInterrupted < Error;
|
15
|
+
end
|
9
16
|
end
|
@@ -2,25 +2,25 @@ require_relative 'response'
|
|
2
2
|
require 'json/add/exception'
|
3
3
|
|
4
4
|
module Unrestful
|
5
|
-
|
6
|
-
|
7
|
-
attr_accessor :message
|
8
|
-
attr_accessor :exception
|
5
|
+
class FailResponse < Unrestful::Response
|
9
6
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
7
|
+
attr_accessor :message
|
8
|
+
attr_accessor :exception
|
9
|
+
|
10
|
+
def self.render(message, exc: nil)
|
11
|
+
obj = Unrestful::FailResponse.new
|
12
|
+
obj.message = message
|
13
|
+
obj.exception = exc if !exc.nil? && Rails.env.development?
|
14
|
+
obj.ok = false
|
15
|
+
|
16
|
+
return obj.as_json
|
17
|
+
end
|
18
|
+
|
19
|
+
def as_json
|
20
|
+
result = {message: message}
|
21
|
+
result.merge!({exception: exception}) unless exception.nil?
|
22
|
+
super.merge(result)
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
26
|
end
|
@@ -3,32 +3,32 @@ require 'net/http'
|
|
3
3
|
require 'uri'
|
4
4
|
|
5
5
|
module Unrestful
|
6
|
-
|
7
|
-
|
6
|
+
class JsonWebToken
|
7
|
+
LEEWAY = 30
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
9
|
+
def self.verify(token)
|
10
|
+
JWT.decode(token, nil,
|
11
|
+
true,
|
12
|
+
algorithm: 'RS256',
|
13
|
+
iss: Unrestful.configuration.issuer,
|
14
|
+
verify_iss: true,
|
15
|
+
aud: Unrestful.configuration.audience,
|
16
|
+
verify_aud: true) do |header|
|
17
|
+
jwks_hash[header['kid']]
|
18
|
+
end
|
19
|
+
end
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
21
|
+
def self.jwks_hash
|
22
|
+
jwks_raw = Net::HTTP.get URI("#{Unrestful.configuration.issuer}.well-known/jwks.json")
|
23
|
+
jwks_keys = Array(JSON.parse(jwks_raw)['keys'])
|
24
|
+
Hash[
|
25
|
+
jwks_keys.map do |k|
|
26
|
+
[
|
27
|
+
k['kid'],
|
28
|
+
OpenSSL::X509::Certificate.new(Base64.decode64(k['x5c'].first)).public_key
|
29
|
+
]
|
30
|
+
end
|
31
|
+
]
|
32
|
+
end
|
33
|
+
end
|
34
34
|
end
|
@@ -2,33 +2,43 @@
|
|
2
2
|
require 'jwt'
|
3
3
|
|
4
4
|
module Unrestful
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
private
|
9
|
-
|
10
|
-
def authenticate_request!
|
11
|
-
@auth_payload, @auth_header = auth_token
|
12
|
-
|
13
|
-
raise AuthError, 'Insufficient scope' unless scope_included
|
14
|
-
end
|
5
|
+
module JwtSecured
|
6
|
+
extend ActiveSupport::Concern
|
15
7
|
|
16
|
-
|
17
|
-
if request.headers['Authorization'].present?
|
18
|
-
request.headers['Authorization'].split(' ').last
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
def auth_token
|
23
|
-
JsonWebToken.verify(http_token)
|
24
|
-
end
|
8
|
+
private
|
25
9
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
10
|
+
def authenticate_request!
|
11
|
+
@auth_payload, @auth_header = auth_token
|
12
|
+
raise AuthError, 'Insufficient scope' unless scope_included
|
13
|
+
end
|
14
|
+
|
15
|
+
def http_token
|
16
|
+
if request.headers['Authorization'].present?
|
17
|
+
# strip off the Bearer
|
18
|
+
request.headers['Authorization'].split(' ').last
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def auth_token
|
23
|
+
JsonWebToken.verify(http_token)
|
24
|
+
end
|
25
|
+
|
26
|
+
def scope_included
|
27
|
+
permissions_required = class_assigned_scopes
|
28
|
+
permissions_present = @auth_payload['permissions'] || []
|
29
|
+
# ensure that we have the required permission to call the method
|
30
|
+
(permissions_present & permissions_required).any?
|
31
|
+
end
|
32
|
+
|
33
|
+
def class_assigned_scopes
|
34
|
+
class_assigned_scopes = self.class.assigned_scopes[@method] || []
|
35
|
+
# ensure that we have a scope defined for this method, and that it has an array value
|
36
|
+
if class_assigned_scopes.nil? || class_assigned_scopes.empty? || !class_assigned_scopes.is_a?(Array)
|
37
|
+
raise "#{self.class.name} MUST declare a \"scopes\" hash that INCLUDES key \"#{@method}\" with value of an array of permissions"
|
38
|
+
end
|
39
|
+
class_assigned_scopes
|
40
|
+
rescue NoMethodError
|
41
|
+
raise "#{self.class.name} MUST implement ::Unrestful::RpcController AND declare \"scopes\" for each method request, with its corresponding array of permissions"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/unrestful/response.rb
CHANGED
@@ -2,18 +2,18 @@ module Unrestful
|
|
2
2
|
class RpcController
|
3
3
|
|
4
4
|
attr_reader :service
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
5
|
+
attr_reader :method
|
6
|
+
attr_reader :request
|
7
|
+
attr_reader :response
|
8
|
+
attr_reader :live
|
9
|
+
attr_reader :async
|
10
|
+
attr_reader :job
|
11
11
|
|
12
12
|
class_attribute :before_method_callbacks, default: ActiveSupport::HashWithIndifferentAccess.new
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
class_attribute :after_method_callbacks, default: ActiveSupport::HashWithIndifferentAccess.new
|
14
|
+
class_attribute :assigned_scopes, default: ActiveSupport::HashWithIndifferentAccess.new
|
15
|
+
class_attribute :live_methods, default: []
|
16
|
+
class_attribute :async_methods, default: []
|
17
17
|
|
18
18
|
def before_callbacks
|
19
19
|
self.class.before_method_callbacks.each do |k, v|
|
@@ -26,36 +26,36 @@ module Unrestful
|
|
26
26
|
self.class.after_method_callbacks.each do |k, v|
|
27
27
|
self.send(k)
|
28
28
|
end
|
29
|
-
|
29
|
+
end
|
30
|
+
|
31
|
+
def write(message)
|
32
|
+
raise NotLiveError unless live
|
33
|
+
msg = message.end_with?("\n") ? message : "#{message}\n"
|
34
|
+
response.stream.write msg
|
35
|
+
end
|
30
36
|
|
31
|
-
def write(message)
|
32
|
-
raise NotLiveError unless live
|
33
|
-
msg = message.end_with?("\n") ? message : "#{message}\n"
|
34
|
-
response.stream.write msg
|
35
|
-
end
|
36
|
-
|
37
37
|
protected
|
38
38
|
|
39
39
|
def self.before_method(method, options = {})
|
40
|
-
self.before_method_callbacks = {
|
40
|
+
self.before_method_callbacks = {method => options}
|
41
41
|
end
|
42
42
|
|
43
43
|
def self.after_method(method, options = {})
|
44
|
-
self.after_method_callbacks = {
|
45
|
-
|
44
|
+
self.after_method_callbacks = {method => options}
|
45
|
+
end
|
46
46
|
|
47
|
-
|
48
|
-
|
49
|
-
|
47
|
+
def self.scopes(scope_list)
|
48
|
+
self.assigned_scopes = ActiveSupport::HashWithIndifferentAccess.new(scope_list)
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.live(live_list)
|
52
|
+
self.live_methods = live_list
|
53
|
+
end
|
50
54
|
|
51
|
-
|
52
|
-
|
53
|
-
|
55
|
+
def self.async(async_list)
|
56
|
+
self.async_methods = async_list
|
57
|
+
end
|
54
58
|
|
55
|
-
def self.async(async_list)
|
56
|
-
self.async_methods = async_list
|
57
|
-
end
|
58
|
-
|
59
59
|
def fail!(message = "")
|
60
60
|
raise ::Unrestful::FailError, message
|
61
61
|
end
|
data/lib/unrestful/utils.rb
CHANGED
@@ -1,19 +1,19 @@
|
|
1
1
|
module Unrestful
|
2
|
-
|
2
|
+
module Utils
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
4
|
+
def watchdog(last_words)
|
5
|
+
yield
|
6
|
+
rescue Exception => exc
|
7
|
+
Rails.logger.debug "#{last_words}: #{exc}"
|
8
|
+
#raise exc
|
9
|
+
end
|
10
|
+
|
11
|
+
def safe_thread(name, &block)
|
12
|
+
Thread.new do
|
13
|
+
Thread.current['unrestful_name'.freeze] = name
|
14
|
+
watchdog(name, &block)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
19
|
end
|
data/lib/unrestful/version.rb
CHANGED
data/lib/unrestful.rb
CHANGED
@@ -3,43 +3,43 @@ require "unrestful/engine"
|
|
3
3
|
Dir[File.join(__dir__, 'unrestful', '*.rb')].each {|file| require file}
|
4
4
|
|
5
5
|
module Unrestful
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
6
|
+
def self.configure(options = {}, &block)
|
7
|
+
@config = Unrestful::Config.new(options)
|
8
|
+
block.call(@config) if block.present?
|
9
|
+
@config
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.configuration
|
13
|
+
@config || Unrestful::Config.new({})
|
14
|
+
end
|
15
|
+
|
16
|
+
class Config
|
17
|
+
def initialize(options)
|
18
|
+
@options = options
|
19
|
+
end
|
20
|
+
|
21
|
+
def issuer
|
22
|
+
@options[:issuer] || ENV.fetch("ISSUER")
|
23
|
+
end
|
24
|
+
|
25
|
+
def issuer=(value)
|
26
|
+
@options[:issuer] = value
|
27
|
+
end
|
28
|
+
|
29
|
+
def audience
|
30
|
+
@options[:audience] || ENV.fetch("AUDIENCE")
|
31
|
+
end
|
32
|
+
|
33
|
+
def audience=(value)
|
34
|
+
@options[:audience] = value
|
35
|
+
end
|
36
|
+
|
37
|
+
def redis_address
|
38
|
+
@options[:redis_address] || ENV.fetch("REDIS_URL") {"redis://localhost:6379/1"}
|
39
|
+
end
|
40
|
+
|
41
|
+
def redis_address=(value)
|
42
|
+
@options[:redis_address] = value
|
43
|
+
end
|
44
|
+
end
|
45
45
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: unrestful
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Khash Sajadi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-07
|
11
|
+
date: 2019-08-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -134,8 +134,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
134
134
|
- !ruby/object:Gem::Version
|
135
135
|
version: '0'
|
136
136
|
requirements: []
|
137
|
-
|
138
|
-
rubygems_version: 2.7.9
|
137
|
+
rubygems_version: 3.0.4
|
139
138
|
signing_key:
|
140
139
|
specification_version: 4
|
141
140
|
summary: Unrestful is a simple RPC framework for Rails
|