unrestful 0.1.3 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|