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.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +127 -0
  3. data/lib/locked-rb.rb +3 -0
  4. data/lib/locked.rb +60 -0
  5. data/lib/locked/api.rb +40 -0
  6. data/lib/locked/api/request.rb +37 -0
  7. data/lib/locked/api/request/build.rb +29 -0
  8. data/lib/locked/api/response.rb +40 -0
  9. data/lib/locked/client.rb +66 -0
  10. data/lib/locked/command.rb +5 -0
  11. data/lib/locked/commands/authenticate.rb +23 -0
  12. data/lib/locked/commands/identify.rb +23 -0
  13. data/lib/locked/commands/review.rb +14 -0
  14. data/lib/locked/configuration.rb +75 -0
  15. data/lib/locked/context/default.rb +40 -0
  16. data/lib/locked/context/merger.rb +14 -0
  17. data/lib/locked/context/sanitizer.rb +23 -0
  18. data/lib/locked/errors.rb +41 -0
  19. data/lib/locked/extractors/client_id.rb +17 -0
  20. data/lib/locked/extractors/headers.rb +24 -0
  21. data/lib/locked/extractors/ip.rb +18 -0
  22. data/lib/locked/failover_auth_response.rb +23 -0
  23. data/lib/locked/header_formatter.rb +9 -0
  24. data/lib/locked/review.rb +11 -0
  25. data/lib/locked/secure_mode.rb +11 -0
  26. data/lib/locked/support/hanami.rb +19 -0
  27. data/lib/locked/support/padrino.rb +19 -0
  28. data/lib/locked/support/rails.rb +13 -0
  29. data/lib/locked/support/sinatra.rb +19 -0
  30. data/lib/locked/utils.rb +55 -0
  31. data/lib/locked/utils/cloner.rb +11 -0
  32. data/lib/locked/utils/merger.rb +23 -0
  33. data/lib/locked/utils/timestamp.rb +12 -0
  34. data/lib/locked/validators/not_supported.rb +16 -0
  35. data/lib/locked/validators/present.rb +16 -0
  36. data/lib/locked/version.rb +5 -0
  37. data/spec/lib/Locked/api/request/build_spec.rb +42 -0
  38. data/spec/lib/Locked/api/request_spec.rb +59 -0
  39. data/spec/lib/Locked/api/response_spec.rb +58 -0
  40. data/spec/lib/Locked/api_spec.rb +37 -0
  41. data/spec/lib/Locked/client_spec.rb +226 -0
  42. data/spec/lib/Locked/command_spec.rb +9 -0
  43. data/spec/lib/Locked/commands/authenticate_spec.rb +95 -0
  44. data/spec/lib/Locked/commands/identify_spec.rb +87 -0
  45. data/spec/lib/Locked/commands/review_spec.rb +24 -0
  46. data/spec/lib/Locked/configuration_spec.rb +146 -0
  47. data/spec/lib/Locked/context/default_spec.rb +35 -0
  48. data/spec/lib/Locked/context/merger_spec.rb +23 -0
  49. data/spec/lib/Locked/context/sanitizer_spec.rb +27 -0
  50. data/spec/lib/Locked/extractors/client_id_spec.rb +62 -0
  51. data/spec/lib/Locked/extractors/headers_spec.rb +26 -0
  52. data/spec/lib/Locked/extractors/ip_spec.rb +27 -0
  53. data/spec/lib/Locked/header_formatter_spec.rb +25 -0
  54. data/spec/lib/Locked/review_spec.rb +19 -0
  55. data/spec/lib/Locked/secure_mode_spec.rb +9 -0
  56. data/spec/lib/Locked/utils/cloner_spec.rb +18 -0
  57. data/spec/lib/Locked/utils/merger_spec.rb +13 -0
  58. data/spec/lib/Locked/utils/timestamp_spec.rb +17 -0
  59. data/spec/lib/Locked/utils_spec.rb +156 -0
  60. data/spec/lib/Locked/validators/not_supported_spec.rb +26 -0
  61. data/spec/lib/Locked/validators/present_spec.rb +33 -0
  62. data/spec/lib/Locked/version_spec.rb +5 -0
  63. data/spec/lib/locked_spec.rb +66 -0
  64. data/spec/spec_helper.rb +22 -0
  65. 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,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Locked
4
+ class HeaderFormatter
5
+ def call(header)
6
+ header.to_s.gsub(/^HTTP(?:_|-)/i, '').split(/_|-/).map(&:capitalize).join('-')
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Locked
4
+ class Review
5
+ def self.retrieve(review_id)
6
+ Locked::API.request(
7
+ Locked::Commands::Review.build(review_id)
8
+ )
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+
5
+ module Locked
6
+ module SecureMode
7
+ def self.signature(user_id)
8
+ OpenSSL::HMAC.hexdigest('sha256', Locked.config.api_key, user_id.to_s)
9
+ end
10
+ end
11
+ 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