locked-rb 0.0.1

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