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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f1f85b74343e706d02705c4994bb54bf9433697b273eff7f28e451c21516fea8
4
- data.tar.gz: b274e109d89b2d89806269627fde55a916bf0b83e4e68f4bea6fe73bbd224caa
3
+ metadata.gz: 8ed14da4951593e6b5461d2d776a16097b46ed7495ab60ca7cae9c24fc778bdc
4
+ data.tar.gz: 2f9dd412dcae54e6a86fff857752898d1f654f2d6acf7cf5f1d7e6d8f6c87bf1
5
5
  SHA512:
6
- metadata.gz: 8889f208b5d6fc05ec1516fcd14b85c0392fef74f379f4f35d7ce8d66404741d93ee3f7d0171f7d777312c90c6235ead34085f52a8628da841b680ad99a9ccf3
7
- data.tar.gz: 45f21e549c14e1497776463adf39704002ea6e2e0351be0224da1095b29ec71fdb93a7ad0d88e462be96bbb4620f87d2fcbe9bfa74fd4053b1a36fba788a2998
6
+ metadata.gz: f58bde1f04744005aeec13d1e46c1002c66de852086b1da36843d66fe501320339f1d8e63e2c5c6380e9943b0abaa13f16a8035a6e9f7d5a0ee55e1622e05f33
7
+ data.tar.gz: d816f606a7aa1e36857c046e76df687d7866d4df5b35085f58642ac7ad9a39b430f737ea1dc6f16ecee0c599acfb85bb4e1a3191b4a131f5f139f1f48ac5fffe
@@ -1,111 +1,111 @@
1
1
  require 'redis'
2
2
 
3
3
  module Unrestful
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
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
@@ -1,9 +1,16 @@
1
1
  module Unrestful
2
- class Error < StandardError; end
3
- class FailError < Error; end
4
- class AuthError < Error; end
5
- class NotLiveError < Error; end
6
- class LiveError < Error; end
7
- class AsyncError < Error; end
8
- class StreamInterrupted < Error; end
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
- class FailResponse < Unrestful::Response
6
-
7
- attr_accessor :message
8
- attr_accessor :exception
5
+ class FailResponse < Unrestful::Response
9
6
 
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
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
- class JsonWebToken
7
- LEEWAY = 30
6
+ class JsonWebToken
7
+ LEEWAY = 30
8
8
 
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
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
- 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
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
- module JwtSecured
6
- extend ActiveSupport::Concern
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
- def http_token
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
- def scope_included
27
- if self.class.assigned_scopes[@method] == nil
28
- false
29
- else
30
- (String(@auth_payload['scope']).split(' ') & (self.class.assigned_scopes[@method])).any?
31
- end
32
- end
33
- end
34
- end
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
@@ -4,8 +4,8 @@ module Unrestful
4
4
  attr_accessor :ok
5
5
 
6
6
  def as_json
7
- { ok: @ok }
7
+ {ok: @ok}
8
8
  end
9
9
 
10
10
  end
11
- end
11
+ end
@@ -2,18 +2,18 @@ module Unrestful
2
2
  class RpcController
3
3
 
4
4
  attr_reader :service
5
- attr_reader :method
6
- attr_reader :request
7
- attr_reader :response
8
- attr_reader :live
9
- attr_reader :async
10
- attr_reader :job
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
- 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: []
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
- end
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 = { method => options }
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 = { method => options }
45
- end
44
+ self.after_method_callbacks = {method => options}
45
+ end
46
46
 
47
- def self.scopes(scope_list)
48
- self.assigned_scopes = ActiveSupport::HashWithIndifferentAccess.new(scope_list)
49
- end
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
- def self.live(live_list)
52
- self.live_methods = live_list
53
- end
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
@@ -14,7 +14,7 @@ module Unrestful
14
14
  end
15
15
 
16
16
  def as_json
17
- super.merge({ payload: payload })
17
+ super.merge({payload: payload})
18
18
  end
19
19
  end
20
- end
20
+ end
@@ -1,19 +1,19 @@
1
1
  module Unrestful
2
- module Utils
2
+ module Utils
3
3
 
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
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
@@ -1,3 +1,3 @@
1
1
  module Unrestful
2
- VERSION = '0.1.3'
2
+ VERSION = '0.1.4'
3
3
  end
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
- 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
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.3
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-16 00:00:00.000000000 Z
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
- rubyforge_project:
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