locked-rb 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|