locked-rb 0.0.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/README.md +127 -0
- data/lib/locked-rb.rb +3 -0
- data/lib/locked.rb +60 -0
- data/lib/locked/api.rb +40 -0
- data/lib/locked/api/request.rb +37 -0
- data/lib/locked/api/request/build.rb +29 -0
- data/lib/locked/api/response.rb +40 -0
- data/lib/locked/client.rb +66 -0
- data/lib/locked/command.rb +5 -0
- data/lib/locked/commands/authenticate.rb +23 -0
- data/lib/locked/commands/identify.rb +23 -0
- data/lib/locked/commands/review.rb +14 -0
- data/lib/locked/configuration.rb +75 -0
- data/lib/locked/context/default.rb +40 -0
- data/lib/locked/context/merger.rb +14 -0
- data/lib/locked/context/sanitizer.rb +23 -0
- data/lib/locked/errors.rb +41 -0
- data/lib/locked/extractors/client_id.rb +17 -0
- data/lib/locked/extractors/headers.rb +24 -0
- data/lib/locked/extractors/ip.rb +18 -0
- data/lib/locked/failover_auth_response.rb +23 -0
- data/lib/locked/header_formatter.rb +9 -0
- data/lib/locked/review.rb +11 -0
- data/lib/locked/secure_mode.rb +11 -0
- data/lib/locked/support/hanami.rb +19 -0
- data/lib/locked/support/padrino.rb +19 -0
- data/lib/locked/support/rails.rb +13 -0
- data/lib/locked/support/sinatra.rb +19 -0
- data/lib/locked/utils.rb +55 -0
- data/lib/locked/utils/cloner.rb +11 -0
- data/lib/locked/utils/merger.rb +23 -0
- data/lib/locked/utils/timestamp.rb +12 -0
- data/lib/locked/validators/not_supported.rb +16 -0
- data/lib/locked/validators/present.rb +16 -0
- data/lib/locked/version.rb +5 -0
- data/spec/lib/Locked/api/request/build_spec.rb +42 -0
- data/spec/lib/Locked/api/request_spec.rb +59 -0
- data/spec/lib/Locked/api/response_spec.rb +58 -0
- data/spec/lib/Locked/api_spec.rb +37 -0
- data/spec/lib/Locked/client_spec.rb +226 -0
- data/spec/lib/Locked/command_spec.rb +9 -0
- data/spec/lib/Locked/commands/authenticate_spec.rb +95 -0
- data/spec/lib/Locked/commands/identify_spec.rb +87 -0
- data/spec/lib/Locked/commands/review_spec.rb +24 -0
- data/spec/lib/Locked/configuration_spec.rb +146 -0
- data/spec/lib/Locked/context/default_spec.rb +35 -0
- data/spec/lib/Locked/context/merger_spec.rb +23 -0
- data/spec/lib/Locked/context/sanitizer_spec.rb +27 -0
- data/spec/lib/Locked/extractors/client_id_spec.rb +62 -0
- data/spec/lib/Locked/extractors/headers_spec.rb +26 -0
- data/spec/lib/Locked/extractors/ip_spec.rb +27 -0
- data/spec/lib/Locked/header_formatter_spec.rb +25 -0
- data/spec/lib/Locked/review_spec.rb +19 -0
- data/spec/lib/Locked/secure_mode_spec.rb +9 -0
- data/spec/lib/Locked/utils/cloner_spec.rb +18 -0
- data/spec/lib/Locked/utils/merger_spec.rb +13 -0
- data/spec/lib/Locked/utils/timestamp_spec.rb +17 -0
- data/spec/lib/Locked/utils_spec.rb +156 -0
- data/spec/lib/Locked/validators/not_supported_spec.rb +26 -0
- data/spec/lib/Locked/validators/present_spec.rb +33 -0
- data/spec/lib/Locked/version_spec.rb +5 -0
- data/spec/lib/locked_spec.rb +66 -0
- data/spec/spec_helper.rb +22 -0
- metadata +133 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Locked
|
4
|
+
module Commands
|
5
|
+
class Identify
|
6
|
+
def initialize(context)
|
7
|
+
@context = context
|
8
|
+
end
|
9
|
+
|
10
|
+
def build(options = {})
|
11
|
+
Locked::Validators::NotSupported.call(options, %i[properties])
|
12
|
+
context = Locked::Context::Merger.call(@context, options[:context])
|
13
|
+
context = Locked::Context::Sanitizer.call(context)
|
14
|
+
|
15
|
+
Locked::Command.new(
|
16
|
+
'identify',
|
17
|
+
options.merge(context: context),
|
18
|
+
:post
|
19
|
+
)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Locked
|
4
|
+
module Commands
|
5
|
+
class Review
|
6
|
+
class << self
|
7
|
+
def build(review_id)
|
8
|
+
Locked::Validators::Present.call({ review_id: review_id }, %i[review_id])
|
9
|
+
Locked::Command.new("reviews/#{review_id}", nil, :get)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Locked
|
4
|
+
# manages configuration variables
|
5
|
+
class Configuration
|
6
|
+
HOST = ENV['RAILS_ENV'] == 'development' ? 'localhost' : 'locked.jp'
|
7
|
+
PORT = ENV['RAILS_ENV'] == 'development' ? 3000 : 443
|
8
|
+
URL_PREFIX = 'api/v1/client'
|
9
|
+
FAILOVER_STRATEGY = :deny
|
10
|
+
REQUEST_TIMEOUT = 1000 # in milliseconds
|
11
|
+
FAILOVER_STRATEGIES = %i[allow deny throw].freeze
|
12
|
+
WHITELISTED = [
|
13
|
+
'User-Agent',
|
14
|
+
'Accept-Language',
|
15
|
+
'Accept-Encoding',
|
16
|
+
'Accept-Charset',
|
17
|
+
'Accept',
|
18
|
+
'Accept-Datetime',
|
19
|
+
'X-Forwarded-For',
|
20
|
+
'Forwarded',
|
21
|
+
'X-Forwarded',
|
22
|
+
'X-Real-IP',
|
23
|
+
'REMOTE_ADDR',
|
24
|
+
'X-Forwarded-For',
|
25
|
+
'CF_CONNECTING_IP'
|
26
|
+
].freeze
|
27
|
+
BLACKLISTED = ['HTTP_COOKIE'].freeze
|
28
|
+
|
29
|
+
attr_accessor :host, :port, :request_timeout, :url_prefix
|
30
|
+
attr_reader :api_key, :whitelisted, :blacklisted, :failover_strategy
|
31
|
+
|
32
|
+
def initialize
|
33
|
+
@formatter = Locked::HeaderFormatter.new
|
34
|
+
@request_timeout = REQUEST_TIMEOUT
|
35
|
+
self.failover_strategy = FAILOVER_STRATEGY
|
36
|
+
self.host = HOST
|
37
|
+
self.port = PORT
|
38
|
+
self.url_prefix = URL_PREFIX
|
39
|
+
self.whitelisted = WHITELISTED
|
40
|
+
self.blacklisted = BLACKLISTED
|
41
|
+
self.api_key = ''
|
42
|
+
end
|
43
|
+
|
44
|
+
def api_key=(value)
|
45
|
+
@api_key = ENV.fetch('X_LOCKED_API_KEY', value).to_s
|
46
|
+
end
|
47
|
+
|
48
|
+
def whitelisted=(value)
|
49
|
+
@whitelisted = (value ? value.map { |header| @formatter.call(header) } : []).freeze
|
50
|
+
end
|
51
|
+
|
52
|
+
def blacklisted=(value)
|
53
|
+
@blacklisted = (value ? value.map { |header| @formatter.call(header) } : []).freeze
|
54
|
+
end
|
55
|
+
|
56
|
+
def valid?
|
57
|
+
!api_key.to_s.empty? && !host.to_s.empty? && !port.to_s.empty?
|
58
|
+
end
|
59
|
+
|
60
|
+
def failover_strategy=(value)
|
61
|
+
@failover_strategy = FAILOVER_STRATEGIES.detect { |strategy| strategy == value.to_sym }
|
62
|
+
raise Locked::ConfigurationError, 'unrecognized failover strategy' if @failover_strategy.nil?
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def respond_to_missing?(method_name, _include_private)
|
68
|
+
/^(\w+)=$/ =~ method_name
|
69
|
+
end
|
70
|
+
|
71
|
+
def method_missing(setting, *_args)
|
72
|
+
raise Locked::ConfigurationError, "there is no such a config #{setting}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Locked
|
4
|
+
module Context
|
5
|
+
class Default
|
6
|
+
def initialize(request, cookies = nil)
|
7
|
+
@client_id = Extractors::ClientId.new(request, cookies || request.cookies).call
|
8
|
+
@headers = Extractors::Headers.new(request).call
|
9
|
+
@request_ip = Extractors::IP.new(request).call
|
10
|
+
end
|
11
|
+
|
12
|
+
def call
|
13
|
+
defaults.merge!(additional_defaults)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def defaults
|
19
|
+
{
|
20
|
+
client_id: @client_id,
|
21
|
+
active: true,
|
22
|
+
origin: 'web',
|
23
|
+
headers: @headers,
|
24
|
+
ip: @request_ip,
|
25
|
+
library: {
|
26
|
+
name: 'locked-rb',
|
27
|
+
version: Locked::VERSION
|
28
|
+
}
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def additional_defaults
|
33
|
+
{}.tap do |result|
|
34
|
+
result[:locale] = @headers['Accept-Language'] if @headers['Accept-Language']
|
35
|
+
result[:user_agent] = @headers['User-Agent'] if @headers['User-Agent']
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Locked
|
4
|
+
module Context
|
5
|
+
class Merger
|
6
|
+
class << self
|
7
|
+
def call(initial_context, request_context)
|
8
|
+
main_context = Locked::Utils::Cloner.call(initial_context)
|
9
|
+
Locked::Utils::Merger.call(main_context, request_context || {})
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Locked
|
4
|
+
module Context
|
5
|
+
# removes not proper active flag values
|
6
|
+
class Sanitizer
|
7
|
+
class << self
|
8
|
+
def call(context)
|
9
|
+
sanitized_active_mode(context) || {}
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def sanitized_active_mode(context)
|
15
|
+
return unless context
|
16
|
+
return context unless context.key?(:active)
|
17
|
+
return context if [true, false].include?(context[:active])
|
18
|
+
context.reject { |key| key == :active }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Locked
|
4
|
+
# general error
|
5
|
+
class Error < RuntimeError; end
|
6
|
+
# Raised when anything is wrong with the request (any unhappy path)
|
7
|
+
# This error indicates that either we would wait too long for a response or something
|
8
|
+
# else happened somewhere in the middle and we weren't able to get the results
|
9
|
+
class RequestError < Locked::Error
|
10
|
+
attr_reader :reason
|
11
|
+
|
12
|
+
# @param reason [Exception] the core exception that causes this error
|
13
|
+
def initialize(reason)
|
14
|
+
@reason = reason
|
15
|
+
end
|
16
|
+
end
|
17
|
+
# security error
|
18
|
+
class SecurityError < Locked::Error; end
|
19
|
+
# wrong configuration error
|
20
|
+
class ConfigurationError < Locked::Error; end
|
21
|
+
# error returned by api
|
22
|
+
class ApiError < Locked::Error; end
|
23
|
+
|
24
|
+
# api error bad request 400
|
25
|
+
class BadRequestError < Locked::ApiError; end
|
26
|
+
# api error forbidden 403
|
27
|
+
class ForbiddenError < Locked::ApiError; end
|
28
|
+
# api error not found 404
|
29
|
+
class NotFoundError < Locked::ApiError; end
|
30
|
+
# api error user unauthorized 419
|
31
|
+
class UserUnauthorizedError < Locked::ApiError; end
|
32
|
+
# api error invalid param 422
|
33
|
+
class InvalidParametersError < Locked::ApiError; end
|
34
|
+
# api error unauthorized 401
|
35
|
+
class UnauthorizedError < Locked::ApiError; end
|
36
|
+
# all internal server errors
|
37
|
+
class InternalServerError < Locked::ApiError; end
|
38
|
+
|
39
|
+
# impersonation command failed
|
40
|
+
class ImpersonationFailed < Locked::ApiError; end
|
41
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Locked
|
4
|
+
module Extractors
|
5
|
+
# used for extraction of cookies and headers from the request
|
6
|
+
class ClientId
|
7
|
+
def initialize(request, cookies)
|
8
|
+
@request = request
|
9
|
+
@cookies = cookies || {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def call
|
13
|
+
@request.env['HTTP_X_LOCKED_CLIENT_ID'] || @cookies['__cid'] || ''
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Locked
|
4
|
+
module Extractors
|
5
|
+
# used for extraction of cookies and headers from the request
|
6
|
+
class Headers
|
7
|
+
def initialize(request)
|
8
|
+
@request = request
|
9
|
+
@request_env = @request.env
|
10
|
+
@formatter = HeaderFormatter.new
|
11
|
+
end
|
12
|
+
|
13
|
+
# Serialize HTTP headers
|
14
|
+
def call
|
15
|
+
@request_env.keys.each_with_object({}) do |header, acc|
|
16
|
+
name = @formatter.call(header)
|
17
|
+
next unless Locked.config.whitelisted.include?(name)
|
18
|
+
next if Locked.config.blacklisted.include?(name)
|
19
|
+
acc[name] = @request_env[header]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Locked
|
4
|
+
module Extractors
|
5
|
+
# used for extraction of ip from the request
|
6
|
+
class IP
|
7
|
+
def initialize(request)
|
8
|
+
@request = request
|
9
|
+
end
|
10
|
+
|
11
|
+
def call
|
12
|
+
return @request.env['HTTP_CF_CONNECTING_IP'] if @request.env['HTTP_CF_CONNECTING_IP']
|
13
|
+
return @request.remote_ip if @request.respond_to?(:remote_ip)
|
14
|
+
@request.ip
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Locked
|
4
|
+
# generate failover authentication response
|
5
|
+
class FailoverAuthResponse
|
6
|
+
def initialize(user_id, strategy: Locked.config.failover_strategy, reason:)
|
7
|
+
@strategy = strategy
|
8
|
+
@reason = reason
|
9
|
+
@user_id = user_id
|
10
|
+
end
|
11
|
+
|
12
|
+
def generate
|
13
|
+
{
|
14
|
+
data: {
|
15
|
+
action: @strategy.to_s,
|
16
|
+
user_id: @user_id,
|
17
|
+
},
|
18
|
+
failover: true,
|
19
|
+
failover_reason: @reason
|
20
|
+
}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Locked
|
4
|
+
module Hanami
|
5
|
+
module Action
|
6
|
+
def locked
|
7
|
+
@locked ||= ::Locked::Client.from_request(request, cookies: (cookies if defined? cookies))
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.included(base)
|
12
|
+
base.configure do
|
13
|
+
controller.prepare do
|
14
|
+
include Locked::Hanami::Action
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Padrino
|
4
|
+
class Application
|
5
|
+
module Locked
|
6
|
+
module Helpers
|
7
|
+
def locked
|
8
|
+
@locked ||= ::Locked::Client.from_request(request)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.registered(app)
|
13
|
+
app.helpers Helpers
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
register locked
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Locked
|
4
|
+
module LockedClient
|
5
|
+
def locked
|
6
|
+
@locked ||= request.env['locked'] || Locked::Client.from_request(request)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
ActiveSupport.on_load(:action_controller) do
|
11
|
+
include LockedClient
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sinatra/base'
|
4
|
+
|
5
|
+
module Sinatra
|
6
|
+
module Locked
|
7
|
+
module Helpers
|
8
|
+
def locked
|
9
|
+
@locked ||= ::Locked::Client.from_request(request)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.registered(app)
|
14
|
+
app.helpers Locked::Helpers
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
register locked
|
19
|
+
end
|