fmrest-core 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FmRest
4
+ module TokenStore
5
+ class Base
6
+ attr_reader :options
7
+
8
+ def initialize(options = {})
9
+ @options = options
10
+ end
11
+
12
+ def load(key)
13
+ raise NotImplementedError
14
+ end
15
+
16
+ def store(key, value)
17
+ raise NotImplementedError
18
+ end
19
+
20
+ def delete(key)
21
+ raise NotImplementedError
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fmrest/token_store/base"
4
+
5
+ module FmRest
6
+ module TokenStore
7
+ class Memory < Base
8
+ def initialize(*args)
9
+ super
10
+ @tokens = {}
11
+ end
12
+
13
+ def delete(key)
14
+ @tokens.delete(key)
15
+ end
16
+
17
+ def load(key)
18
+ @tokens[key]
19
+ end
20
+
21
+ def store(key, value)
22
+ @tokens[key] = value
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fmrest/token_store/base"
4
+ require "moneta"
5
+
6
+ module FmRest
7
+ module TokenStore
8
+ class Moneta < Base
9
+ DEFAULT_ADAPTER = :Memory
10
+ DEFAULT_PREFIX = "fmrest-token:".freeze
11
+
12
+ attr_reader :moneta
13
+
14
+ # @param options [Hash]
15
+ # Options to pass to `Moneta.new`
16
+ # @option options [Symbol] :adapter (:Memory)
17
+ # The Moneta adapter to use
18
+ # @option options [String] :prefix (DEFAULT_PREFIX)
19
+ # The prefix to use for keys
20
+ def initialize(options = {})
21
+ options = options.dup
22
+ super(options)
23
+ adapter = options.delete(:adapter) || DEFAULT_ADAPTER
24
+ options[:prefix] ||= DEFAULT_PREFIX
25
+ @moneta = ::Moneta.new(adapter, options)
26
+ end
27
+
28
+ def load(key)
29
+ moneta[key]
30
+ end
31
+
32
+ def delete(key)
33
+ moneta.delete(key)
34
+ end
35
+
36
+ def store(key, value)
37
+ moneta[key] = value
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "singleton"
4
+
5
+ module FmRest
6
+ module TokenStore
7
+ module Null < Base
8
+ include Singleton
9
+
10
+ def delete(key)
11
+ end
12
+
13
+ def load(key)
14
+ end
15
+
16
+ def store(key, value)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fmrest/token_store/base"
4
+ require "redis" unless defined?(MockRedis)
5
+
6
+ module FmRest
7
+ module TokenStore
8
+ class Redis < Base
9
+ DEFAULT_PREFIX = "fmrest-token:".freeze
10
+
11
+ STORE_OPTIONS = [:redis, :prefix].freeze
12
+
13
+ def initialize(options = {})
14
+ super
15
+ @redis = @options[:redis] || ::Redis.new(options_for_redis)
16
+ @prefix = @options[:prefix] || DEFAULT_PREFIX
17
+ end
18
+
19
+ def load(key)
20
+ @redis.get(prefix_key(key))
21
+ end
22
+
23
+ def store(key, value)
24
+ @redis.set(prefix_key(key), value)
25
+ value
26
+ end
27
+
28
+ def delete(key)
29
+ @redis.del(prefix_key(key))
30
+ end
31
+
32
+ private
33
+
34
+ def options_for_redis
35
+ @options.dup.tap do |options|
36
+ STORE_OPTIONS.each { |opt| options.delete(opt) }
37
+ end
38
+ end
39
+
40
+ def prefix_key(key)
41
+ "#{@prefix}#{key}"
42
+ end
43
+ end
44
+ end
45
+ end
data/lib/fmrest/v1.rb ADDED
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fmrest/v1/connection"
4
+ require "fmrest/v1/paths"
5
+ require "fmrest/v1/container_fields"
6
+ require "fmrest/v1/utils"
7
+ require "fmrest/v1/dates"
8
+ require "fmrest/v1/auth"
9
+
10
+ module FmRest
11
+ module V1
12
+ extend Connection
13
+ extend Paths
14
+ extend ContainerFields
15
+ extend Utils
16
+ extend Dates
17
+ extend Auth
18
+
19
+ autoload :TokenSession, "fmrest/v1/token_session"
20
+ autoload :RaiseErrors, "fmrest/v1/raise_errors"
21
+ autoload :TypeCoercer, "fmrest/v1/type_coercer"
22
+ end
23
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FmRest
4
+ module V1
5
+ module Auth
6
+ # Requests a token through basic auth
7
+ #
8
+ # @param connection [Faraday] the auth connection to use for
9
+ # the request
10
+ # @return The token if successful
11
+ # @return `false` if authentication failed
12
+ def request_auth_token(connection = FmRest::V1.auth_connection)
13
+ request_auth_token!(connection)
14
+ rescue FmRest::APIError::AccountError
15
+ false
16
+ end
17
+
18
+ # Requests a token through basic auth, raising
19
+ # `FmRest::APIError::AccountError` if auth fails
20
+ #
21
+ # @param (see #request_auth_token)
22
+ # @return The token if successful
23
+ # @raise [FmRest::APIError::AccountError] if authentication failed
24
+ def request_auth_token!(connection = FmRest.V1.auth_connection)
25
+ resp = connection.post(V1.session_path)
26
+ resp.body["response"]["token"]
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ module FmRest
6
+ module V1
7
+ module Connection
8
+ BASE_PATH = "/fmi/data/v1"
9
+ DATABASES_PATH = "#{BASE_PATH}/databases"
10
+
11
+ AUTH_HEADERS = { "Content-Type" => "application/json" }.freeze
12
+
13
+ # Builds a complete DAPI Faraday connection with middleware already
14
+ # configured to handle authentication, JSON parsing, logging and DAPI
15
+ # error handling. A block can be optionally given for additional
16
+ # middleware configuration
17
+ #
18
+ # @option (see #base_connection)
19
+ # @return (see #base_connection)
20
+ def build_connection(settings = FmRest.default_connection_settings, &block)
21
+ settings = ConnectionSettings.wrap(settings)
22
+
23
+ base_connection(settings) do |conn|
24
+ conn.use RaiseErrors
25
+ conn.use TokenSession, settings
26
+
27
+ # The EncodeJson and Multipart middlewares only encode the request
28
+ # when the content type matches, so we can have them both here and
29
+ # still play nice with each other, we just need to set the content
30
+ # type to multipart/form-data when we want to submit a container
31
+ # field
32
+ conn.request :multipart
33
+ conn.request :json
34
+
35
+ # Allow overriding the default response middleware
36
+ if block_given?
37
+ yield conn, settings
38
+ else
39
+ conn.use TypeCoercer, settings
40
+ conn.response :json
41
+ end
42
+
43
+ if settings.log
44
+ conn.response :logger, nil, bodies: true, headers: true
45
+ end
46
+
47
+ conn.adapter Faraday.default_adapter
48
+ end
49
+ end
50
+
51
+ # Builds a Faraday connection to use for DAPI basic auth login
52
+ #
53
+ # @option (see #base_connection)
54
+ # @return (see #base_connection)
55
+ def auth_connection(settings = FmRest.default_connection_settings)
56
+ settings = ConnectionSettings.wrap(settings)
57
+
58
+ base_connection(settings, { headers: AUTH_HEADERS }) do |conn|
59
+ conn.use RaiseErrors
60
+
61
+ conn.basic_auth settings.username!, settings.password!
62
+
63
+ if settings.log
64
+ conn.response :logger, nil, bodies: true, headers: true
65
+ end
66
+
67
+ conn.response :json
68
+ conn.adapter Faraday.default_adapter
69
+ end
70
+ end
71
+
72
+ # Builds a base Faraday connection with base URL constructed from
73
+ # connection settings and passes it the given block
74
+ #
75
+ # @option settings [String] :host The hostname for the FM server
76
+ # @option settings [String] :database The FM database name
77
+ # @option settings [String] :username The username for DAPI authentication
78
+ # @option settings [String] :account_name Alias of :username for
79
+ # compatibility with Rfm gem
80
+ # @option settings [String] :password The password for DAPI authentication
81
+ # @option settings [String] :ssl SSL settings to forward to the Faraday
82
+ # connection
83
+ # @option settings [String] :proxy Proxy options to forward to the Faraday
84
+ # connection
85
+ # @param faraday_options [Hash] additional options for Faraday object
86
+ # @return [Faraday] The new Faraday connection
87
+ def base_connection(settings = FmRest.default_connection_settings, faraday_options = nil, &block)
88
+ settings = ConnectionSettings.wrap(settings)
89
+
90
+ host = settings.host!
91
+
92
+ # Default to HTTPS
93
+ scheme = "https"
94
+
95
+ if host.match(/\Ahttps?:\/\//)
96
+ uri = URI(host)
97
+ host = uri.hostname
98
+ host += ":#{uri.port}" if uri.port != uri.default_port
99
+ scheme = uri.scheme
100
+ end
101
+
102
+ faraday_options = (faraday_options || {}).dup
103
+ faraday_options[:ssl] = settings.ssl if settings.ssl?
104
+ faraday_options[:proxy] = settings.proxy if settings.proxy?
105
+
106
+ database = V1.url_encode(settings.database!)
107
+
108
+ Faraday.new(
109
+ "#{scheme}://#{host}#{DATABASES_PATH}/#{database}/".freeze,
110
+ faraday_options,
111
+ &block
112
+ )
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FmRest
4
+ module V1
5
+ module ContainerFields
6
+ DEFAULT_UPLOAD_CONTENT_TYPE = "application/octet-stream".freeze
7
+
8
+ # Given a container field URL it tries to fetch it and returns an IO
9
+ # object with its body content (see Ruby's OpenURI for how the IO object
10
+ # is extended with useful HTTP response information).
11
+ #
12
+ # This method uses OpenURI instead of Faraday for fetching the actual
13
+ # container file.
14
+ #
15
+ # @raise [FmRest::ContainerFieldError] if any step fails
16
+ # @param container_field_url [String] The URL to the container to
17
+ # download
18
+ # @param base_connection [Faraday::Connection] An optional Faraday
19
+ # connection to use as base settings for the container requests, useful
20
+ # if you need to set SSL or proxy settings. If given, this connection
21
+ # will not be used directly, but rather a new one with copied SSL and
22
+ # proxy options. If omitted, `FmRest.default_connection_settings`'s
23
+ # `:ssl` and `:proxy` options will be used instead (if available)
24
+ # @return [IO] The contents of the container
25
+ def fetch_container_data(container_field_url, base_connection = nil)
26
+ require "open-uri"
27
+
28
+ begin
29
+ url = URI(container_field_url)
30
+ rescue ::URI::InvalidURIError
31
+ raise FmRest::ContainerFieldError, "Invalid container field URL `#{container_field_url}'"
32
+ end
33
+
34
+ # Make sure we don't try to open anything on the file:/ URI scheme
35
+ unless url.scheme.match(/\Ahttps?\Z/)
36
+ raise FmRest::ContainerFieldError, "Container URL is not HTTP (#{container_field_url})"
37
+ end
38
+
39
+ ssl_options = base_connection && base_connection.ssl && base_connection.ssl.to_hash
40
+ proxy_options = base_connection && base_connection.proxy && base_connection.proxy.to_hash
41
+
42
+ conn =
43
+ Faraday.new(nil,
44
+ ssl: ssl_options || FmRest.default_connection_settings[:ssl],
45
+ proxy: proxy_options || FmRest.default_connection_settings[:proxy]
46
+ )
47
+
48
+ # Requesting the container URL with no cookie set will respond with a
49
+ # redirect and a session cookie
50
+ cookie_response = conn.get url
51
+
52
+ unless cookie = cookie_response.headers["Set-Cookie"]
53
+ raise FmRest::ContainerFieldError, "Container field's initial request didn't return a session cookie, the URL may be stale (try downloading it again immediately after retrieving the record)"
54
+ end
55
+
56
+ # Now request the URL again with the proper session cookie using
57
+ # OpenURI, which wraps the response in an IO object which also responds
58
+ # to #content_type
59
+ url.open(faraday_connection_to_openuri_options(conn).merge("Cookie" => cookie))
60
+ end
61
+
62
+ # Handles the core logic of uploading a file into a container field
63
+ #
64
+ # @param connection [Faraday::Connection] the Faraday connection to use
65
+ # @param container_path [String] the path to the container
66
+ # @param filename_or_io [String, IO] a path to the file to upload or an
67
+ # IO object
68
+ # @param options [Hash]
69
+ # @option options [String] :content_type (DEFAULT_UPLOAD_CONTENT_TYPE)
70
+ # The content type for the uploaded file
71
+ # @option options [String] :filename The filename to use for the uploaded
72
+ # file, defaults to `filename_or_io.original_filename` if available
73
+ def upload_container_data(connection, container_path, filename_or_io, options = {})
74
+ content_type = options[:content_type] || DEFAULT_UPLOAD_CONTENT_TYPE
75
+
76
+ connection.post do |request|
77
+ request.url container_path
78
+ request.headers['Content-Type'] = ::Faraday::Request::Multipart.mime_type
79
+
80
+ filename = options[:filename] || filename_or_io.try(:original_filename)
81
+
82
+ request.body = { upload: Faraday::UploadIO.new(filename_or_io, content_type, filename) }
83
+ end
84
+ end
85
+
86
+ private
87
+
88
+ # Copies a Faraday::Connection's relevant options to
89
+ # OpenURI::OpenRead#open format
90
+ #
91
+ def faraday_connection_to_openuri_options(conn)
92
+ openuri_opts = {}
93
+
94
+ if !conn.ssl.empty?
95
+ openuri_opts[:ssl_verify_mode] =
96
+ conn.ssl.fetch(:verify, true) ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
97
+
98
+ openuri_opts[:ssl_ca_cert] = conn.ssl.cert_store if conn.ssl.cert_store
99
+ end
100
+
101
+ if conn.proxy && !conn.proxy.empty?
102
+ if conn.proxy.user && conn.proxy.password
103
+ openuri_opts[:proxy_http_basic_authentication] =
104
+ [conn.proxy.uri.tap { |u| u.userinfo = ""}, conn.proxy.user, conn.proxy.password]
105
+ else
106
+ openuri_opts[:proxy] = conn.proxy.uri
107
+ end
108
+ end
109
+
110
+ openuri_opts
111
+ end
112
+ end
113
+ end
114
+ end