maestrano 0.1.0
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/.gitignore +34 -0
 - data/Gemfile +2 -0
 - data/Gemfile.lock +43 -0
 - data/LICENSE +21 -0
 - data/README.md +4 -0
 - data/Rakefile +32 -0
 - data/bin/maestrano-console +9 -0
 - data/lib/maestrano.rb +114 -0
 - data/lib/maestrano/account/bill.rb +14 -0
 - data/lib/maestrano/api/error/authentication_error.rb +8 -0
 - data/lib/maestrano/api/error/base_error.rb +24 -0
 - data/lib/maestrano/api/error/connection_error.rb +8 -0
 - data/lib/maestrano/api/error/invalid_request_error.rb +14 -0
 - data/lib/maestrano/api/list_object.rb +37 -0
 - data/lib/maestrano/api/object.rb +187 -0
 - data/lib/maestrano/api/operation/base.rb +216 -0
 - data/lib/maestrano/api/operation/create.rb +18 -0
 - data/lib/maestrano/api/operation/delete.rb +13 -0
 - data/lib/maestrano/api/operation/list.rb +18 -0
 - data/lib/maestrano/api/operation/update.rb +59 -0
 - data/lib/maestrano/api/resource.rb +39 -0
 - data/lib/maestrano/api/util.rb +121 -0
 - data/lib/maestrano/saml/attribute_value.rb +15 -0
 - data/lib/maestrano/saml/metadata.rb +64 -0
 - data/lib/maestrano/saml/request.rb +93 -0
 - data/lib/maestrano/saml/response.rb +201 -0
 - data/lib/maestrano/saml/schemas/saml20assertion_schema.xsd +283 -0
 - data/lib/maestrano/saml/schemas/saml20protocol_schema.xsd +302 -0
 - data/lib/maestrano/saml/schemas/xenc_schema.xsd +146 -0
 - data/lib/maestrano/saml/schemas/xmldsig_schema.xsd +318 -0
 - data/lib/maestrano/saml/settings.rb +37 -0
 - data/lib/maestrano/saml/validation_error.rb +7 -0
 - data/lib/maestrano/sso.rb +81 -0
 - data/lib/maestrano/sso/base_group.rb +31 -0
 - data/lib/maestrano/sso/base_user.rb +75 -0
 - data/lib/maestrano/sso/group.rb +24 -0
 - data/lib/maestrano/sso/session.rb +63 -0
 - data/lib/maestrano/sso/user.rb +34 -0
 - data/lib/maestrano/version.rb +3 -0
 - data/lib/maestrano/xml_security/signed_document.rb +170 -0
 - data/maestrano.gemspec +32 -0
 - data/test/helpers/api_helpers.rb +82 -0
 - data/test/helpers/saml_helpers.rb +62 -0
 - data/test/maestrano/account/bill_test.rb +48 -0
 - data/test/maestrano/api/list_object_test.rb +20 -0
 - data/test/maestrano/api/object_test.rb +28 -0
 - data/test/maestrano/api/resource_test.rb +343 -0
 - data/test/maestrano/api/util_test.rb +31 -0
 - data/test/maestrano/maestrano_test.rb +49 -0
 - data/test/maestrano/saml/request_test.rb +168 -0
 - data/test/maestrano/saml/response_test.rb +290 -0
 - data/test/maestrano/saml/settings_test.rb +51 -0
 - data/test/maestrano/sso/base_group_test.rb +54 -0
 - data/test/maestrano/sso/base_user_test.rb +114 -0
 - data/test/maestrano/sso/group_test.rb +47 -0
 - data/test/maestrano/sso/session_test.rb +108 -0
 - data/test/maestrano/sso/user_test.rb +65 -0
 - data/test/maestrano/sso_test.rb +81 -0
 - data/test/maestrano/xml_security/signed_document.rb +163 -0
 - data/test/support/saml/certificates/certificate1 +12 -0
 - data/test/support/saml/certificates/r1_certificate2_base64 +1 -0
 - data/test/support/saml/responses/adfs_response_sha1.xml +46 -0
 - data/test/support/saml/responses/adfs_response_sha256.xml +46 -0
 - data/test/support/saml/responses/adfs_response_sha384.xml +46 -0
 - data/test/support/saml/responses/adfs_response_sha512.xml +46 -0
 - data/test/support/saml/responses/no_signature_ns.xml +48 -0
 - data/test/support/saml/responses/open_saml_response.xml +56 -0
 - data/test/support/saml/responses/r1_response6.xml.base64 +1 -0
 - data/test/support/saml/responses/response1.xml.base64 +1 -0
 - data/test/support/saml/responses/response2.xml.base64 +79 -0
 - data/test/support/saml/responses/response3.xml.base64 +66 -0
 - data/test/support/saml/responses/response4.xml.base64 +93 -0
 - data/test/support/saml/responses/response5.xml.base64 +102 -0
 - data/test/support/saml/responses/response_with_ampersands.xml +139 -0
 - data/test/support/saml/responses/response_with_ampersands.xml.base64 +93 -0
 - data/test/support/saml/responses/response_with_multiple_attribute_values.xml +57 -0
 - data/test/support/saml/responses/simple_saml_php.xml +71 -0
 - data/test/support/saml/responses/starfield_response.xml.base64 +1 -0
 - data/test/support/saml/responses/wrapped_response_2.xml.base64 +150 -0
 - data/test/test_helper.rb +46 -0
 - metadata +305 -0
 
| 
         @@ -0,0 +1,216 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Maestrano
         
     | 
| 
      
 2 
     | 
    
         
            +
              module API
         
     | 
| 
      
 3 
     | 
    
         
            +
                module Operation
         
     | 
| 
      
 4 
     | 
    
         
            +
                  module Base
         
     | 
| 
      
 5 
     | 
    
         
            +
                    # class << self
         
     | 
| 
      
 6 
     | 
    
         
            +
                    #   attr_accessor :api_key, :api_base, :verify_ssl_certs, :api_version
         
     | 
| 
      
 7 
     | 
    
         
            +
                    # end
         
     | 
| 
      
 8 
     | 
    
         
            +
                    
         
     | 
| 
      
 9 
     | 
    
         
            +
                    def self.api_url(url='')
         
     | 
| 
      
 10 
     | 
    
         
            +
                      Maestrano.param('api_host') + Maestrano.param('api_base') + url
         
     | 
| 
      
 11 
     | 
    
         
            +
                    end
         
     | 
| 
      
 12 
     | 
    
         
            +
                    
         
     | 
| 
      
 13 
     | 
    
         
            +
                    # Perform remote request
         
     | 
| 
      
 14 
     | 
    
         
            +
                    def self.request(method, url, api_key, params={}, headers={})
         
     | 
| 
      
 15 
     | 
    
         
            +
                      unless api_key ||= Maestrano.param('api_key')
         
     | 
| 
      
 16 
     | 
    
         
            +
                        raise Maestrano::API::Error::AuthenticationError.new('No API key provided.')
         
     | 
| 
      
 17 
     | 
    
         
            +
                      end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                      request_opts = { :verify_ssl => false }
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                      if self.ssl_preflight_passed?
         
     | 
| 
      
 22 
     | 
    
         
            +
                        request_opts.update(
         
     | 
| 
      
 23 
     | 
    
         
            +
                          verify_ssl: OpenSSL::SSL::VERIFY_PEER,
         
     | 
| 
      
 24 
     | 
    
         
            +
                          ssl_ca_file: Maestrano.param('ssl_bundle_path')
         
     | 
| 
      
 25 
     | 
    
         
            +
                        )
         
     | 
| 
      
 26 
     | 
    
         
            +
                      end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                      params = Util.objects_to_ids(params)
         
     | 
| 
      
 29 
     | 
    
         
            +
                      url = api_url(url)
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                      case method.to_s.downcase.to_sym
         
     | 
| 
      
 32 
     | 
    
         
            +
                      when :get, :head, :delete
         
     | 
| 
      
 33 
     | 
    
         
            +
                        # Make params into GET parameters
         
     | 
| 
      
 34 
     | 
    
         
            +
                        url += "#{URI.parse(url).query ? '&' : '?'}#{uri_encode(params)}" if params && params.any?
         
     | 
| 
      
 35 
     | 
    
         
            +
                        payload = nil
         
     | 
| 
      
 36 
     | 
    
         
            +
                      else
         
     | 
| 
      
 37 
     | 
    
         
            +
                        payload = uri_encode(params)
         
     | 
| 
      
 38 
     | 
    
         
            +
                      end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                      request_opts.update(:headers => request_headers(api_key).update(headers),
         
     | 
| 
      
 41 
     | 
    
         
            +
                                          :method => method, :open_timeout => 30,
         
     | 
| 
      
 42 
     | 
    
         
            +
                                          :payload => payload, :url => url, :timeout => 80)
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 45 
     | 
    
         
            +
                        response = execute_request(request_opts)
         
     | 
| 
      
 46 
     | 
    
         
            +
                      rescue SocketError => e
         
     | 
| 
      
 47 
     | 
    
         
            +
                        handle_restclient_error(e)
         
     | 
| 
      
 48 
     | 
    
         
            +
                      rescue NoMethodError => e
         
     | 
| 
      
 49 
     | 
    
         
            +
                        # Work around RestClient bug
         
     | 
| 
      
 50 
     | 
    
         
            +
                        if e.message =~ /\WRequestFailed\W/
         
     | 
| 
      
 51 
     | 
    
         
            +
                          e = APIConnectionError.new('Unexpected HTTP response code')
         
     | 
| 
      
 52 
     | 
    
         
            +
                          handle_restclient_error(e)
         
     | 
| 
      
 53 
     | 
    
         
            +
                        else
         
     | 
| 
      
 54 
     | 
    
         
            +
                          raise
         
     | 
| 
      
 55 
     | 
    
         
            +
                        end
         
     | 
| 
      
 56 
     | 
    
         
            +
                      rescue RestClient::ExceptionWithResponse => e
         
     | 
| 
      
 57 
     | 
    
         
            +
                        if rcode = e.http_code and rbody = e.http_body
         
     | 
| 
      
 58 
     | 
    
         
            +
                          handle_api_error(rcode, rbody)
         
     | 
| 
      
 59 
     | 
    
         
            +
                        else
         
     | 
| 
      
 60 
     | 
    
         
            +
                          handle_restclient_error(e)
         
     | 
| 
      
 61 
     | 
    
         
            +
                        end
         
     | 
| 
      
 62 
     | 
    
         
            +
                      rescue RestClient::Exception, Errno::ECONNREFUSED => e
         
     | 
| 
      
 63 
     | 
    
         
            +
                        handle_restclient_error(e)
         
     | 
| 
      
 64 
     | 
    
         
            +
                      end
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                      [parse(response), api_key]
         
     | 
| 
      
 67 
     | 
    
         
            +
                    end
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                    private
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                    def self.ssl_preflight_passed?
         
     | 
| 
      
 72 
     | 
    
         
            +
                      if !Maestrano.param('verify_ssl_certs')
         
     | 
| 
      
 73 
     | 
    
         
            +
                        #$stderr.puts "WARNING: Running without SSL cert verification. " +
         
     | 
| 
      
 74 
     | 
    
         
            +
                        #  "Execute 'Maestrano.configure { |config| config.verify_ssl_certs = true' } to enable verification."
         
     | 
| 
      
 75 
     | 
    
         
            +
                        return false
         
     | 
| 
      
 76 
     | 
    
         
            +
                      elsif !Util.file_readable(Maestrano.param('ssl_bundle_path'))
         
     | 
| 
      
 77 
     | 
    
         
            +
                        $stderr.puts "WARNING: Running without SSL cert verification " +
         
     | 
| 
      
 78 
     | 
    
         
            +
                          "because #{Maestrano.param('ssl_bundle_path')} isn't readable"
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                        return false
         
     | 
| 
      
 81 
     | 
    
         
            +
                      end
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
                      return true
         
     | 
| 
      
 84 
     | 
    
         
            +
                    end
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
                    def self.user_agent
         
     | 
| 
      
 87 
     | 
    
         
            +
                      @uname ||= get_uname
         
     | 
| 
      
 88 
     | 
    
         
            +
                      lang_version = "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})"
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
                      {
         
     | 
| 
      
 91 
     | 
    
         
            +
                        :bindings_version => Maestrano::VERSION,
         
     | 
| 
      
 92 
     | 
    
         
            +
                        :lang => 'ruby',
         
     | 
| 
      
 93 
     | 
    
         
            +
                        :lang_version => lang_version,
         
     | 
| 
      
 94 
     | 
    
         
            +
                        :platform => RUBY_PLATFORM,
         
     | 
| 
      
 95 
     | 
    
         
            +
                        :publisher => 'maestrano',
         
     | 
| 
      
 96 
     | 
    
         
            +
                        :uname => @uname
         
     | 
| 
      
 97 
     | 
    
         
            +
                      }
         
     | 
| 
      
 98 
     | 
    
         
            +
             
     | 
| 
      
 99 
     | 
    
         
            +
                    end
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
                    def self.get_uname
         
     | 
| 
      
 102 
     | 
    
         
            +
                      `uname -a 2>/dev/null`.strip if RUBY_PLATFORM =~ /linux|darwin/i
         
     | 
| 
      
 103 
     | 
    
         
            +
                    rescue Errno::ENOMEM => ex # couldn't create subprocess
         
     | 
| 
      
 104 
     | 
    
         
            +
                      "uname lookup failed"
         
     | 
| 
      
 105 
     | 
    
         
            +
                    end
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
                    def self.uri_encode(params)
         
     | 
| 
      
 108 
     | 
    
         
            +
                      Util.flatten_params(params).
         
     | 
| 
      
 109 
     | 
    
         
            +
                        map { |k,v| "#{k}=#{Util.url_encode(v)}" }.join('&')
         
     | 
| 
      
 110 
     | 
    
         
            +
                    end
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
                    def self.request_headers(api_key)
         
     | 
| 
      
 113 
     | 
    
         
            +
                      headers = {
         
     | 
| 
      
 114 
     | 
    
         
            +
                        :user_agent => "Maestrano/v1 RubyBindings/#{Maestrano::VERSION}",
         
     | 
| 
      
 115 
     | 
    
         
            +
                        :authorization => "Basic #{Base64.encode64(api_key + ':')}",
         
     | 
| 
      
 116 
     | 
    
         
            +
                        :content_type => 'application/x-www-form-urlencoded'
         
     | 
| 
      
 117 
     | 
    
         
            +
                      }
         
     | 
| 
      
 118 
     | 
    
         
            +
             
     | 
| 
      
 119 
     | 
    
         
            +
                      api_version = Maestrano.param('api_version')
         
     | 
| 
      
 120 
     | 
    
         
            +
                      headers[:maestrano_version] = api_version if api_version
         
     | 
| 
      
 121 
     | 
    
         
            +
             
     | 
| 
      
 122 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 123 
     | 
    
         
            +
                        headers.update(:x_maestrano_client_user_agent => JSON.generate(user_agent))
         
     | 
| 
      
 124 
     | 
    
         
            +
                      rescue => e
         
     | 
| 
      
 125 
     | 
    
         
            +
                        headers.update(:x_maestrano_client_raw_user_agent => user_agent.inspect,
         
     | 
| 
      
 126 
     | 
    
         
            +
                                       :error => "#{e} (#{e.class})")
         
     | 
| 
      
 127 
     | 
    
         
            +
                      end
         
     | 
| 
      
 128 
     | 
    
         
            +
                    end
         
     | 
| 
      
 129 
     | 
    
         
            +
             
     | 
| 
      
 130 
     | 
    
         
            +
                    def self.execute_request(opts)
         
     | 
| 
      
 131 
     | 
    
         
            +
                      RestClient::Request.execute(opts)
         
     | 
| 
      
 132 
     | 
    
         
            +
                    end
         
     | 
| 
      
 133 
     | 
    
         
            +
             
     | 
| 
      
 134 
     | 
    
         
            +
                    def self.parse(response)
         
     | 
| 
      
 135 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 136 
     | 
    
         
            +
                        # Would use :symbolize_names => true, but apparently there is
         
     | 
| 
      
 137 
     | 
    
         
            +
                        # some library out there that makes symbolize_names not work.
         
     | 
| 
      
 138 
     | 
    
         
            +
                        response = JSON.parse(response.body)
         
     | 
| 
      
 139 
     | 
    
         
            +
                      rescue JSON::ParserError
         
     | 
| 
      
 140 
     | 
    
         
            +
                        raise general_api_error(response.code, response.body)
         
     | 
| 
      
 141 
     | 
    
         
            +
                      end
         
     | 
| 
      
 142 
     | 
    
         
            +
             
     | 
| 
      
 143 
     | 
    
         
            +
                      response = Util.symbolize_names(response)
         
     | 
| 
      
 144 
     | 
    
         
            +
                      response[:data]
         
     | 
| 
      
 145 
     | 
    
         
            +
                    end
         
     | 
| 
      
 146 
     | 
    
         
            +
             
     | 
| 
      
 147 
     | 
    
         
            +
                    def self.general_api_error(rcode, rbody)
         
     | 
| 
      
 148 
     | 
    
         
            +
                      Maestrano::API::Error::BaseError.new("Invalid response object from API: #{rbody.inspect} " +
         
     | 
| 
      
 149 
     | 
    
         
            +
                                   "(HTTP response code was #{rcode})", rcode, rbody)
         
     | 
| 
      
 150 
     | 
    
         
            +
                    end
         
     | 
| 
      
 151 
     | 
    
         
            +
             
     | 
| 
      
 152 
     | 
    
         
            +
                    def self.handle_api_error(rcode, rbody)
         
     | 
| 
      
 153 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 154 
     | 
    
         
            +
                        error_obj = JSON.parse(rbody)
         
     | 
| 
      
 155 
     | 
    
         
            +
                        error_obj = Util.symbolize_names(error_obj)
         
     | 
| 
      
 156 
     | 
    
         
            +
                        errors = error_obj[:errors] or raise Maestrano::API::Error::BaseError.new # escape from parsing
         
     | 
| 
      
 157 
     | 
    
         
            +
             
     | 
| 
      
 158 
     | 
    
         
            +
                      rescue JSON::ParserError, Maestrano::API::Error::BaseError
         
     | 
| 
      
 159 
     | 
    
         
            +
                        raise general_api_error(rcode, rbody)
         
     | 
| 
      
 160 
     | 
    
         
            +
                      end
         
     | 
| 
      
 161 
     | 
    
         
            +
             
     | 
| 
      
 162 
     | 
    
         
            +
                      case rcode
         
     | 
| 
      
 163 
     | 
    
         
            +
                      when 400, 404
         
     | 
| 
      
 164 
     | 
    
         
            +
                        raise invalid_request_error(errors, rcode, rbody, error_obj)
         
     | 
| 
      
 165 
     | 
    
         
            +
                      when 401
         
     | 
| 
      
 166 
     | 
    
         
            +
                        raise authentication_error(errors, rcode, rbody, error_obj)
         
     | 
| 
      
 167 
     | 
    
         
            +
                      else
         
     | 
| 
      
 168 
     | 
    
         
            +
                        raise api_error(errors, rcode, rbody, error_obj)
         
     | 
| 
      
 169 
     | 
    
         
            +
                      end
         
     | 
| 
      
 170 
     | 
    
         
            +
             
     | 
| 
      
 171 
     | 
    
         
            +
                    end
         
     | 
| 
      
 172 
     | 
    
         
            +
             
     | 
| 
      
 173 
     | 
    
         
            +
                    def self.invalid_request_error(errors, rcode, rbody, error_obj)
         
     | 
| 
      
 174 
     | 
    
         
            +
                      Maestrano::API::Error::InvalidRequestError.new(errors.first.join(" "), errors.keys.first.to_s, rcode,
         
     | 
| 
      
 175 
     | 
    
         
            +
                                              rbody, error_obj)
         
     | 
| 
      
 176 
     | 
    
         
            +
                    end
         
     | 
| 
      
 177 
     | 
    
         
            +
             
     | 
| 
      
 178 
     | 
    
         
            +
                    def self.authentication_error(errors, rcode, rbody, error_obj)
         
     | 
| 
      
 179 
     | 
    
         
            +
                      Maestrano::API::Error::AuthenticationError.new(errors.first.join(" "), rcode, rbody, error_obj)
         
     | 
| 
      
 180 
     | 
    
         
            +
                    end
         
     | 
| 
      
 181 
     | 
    
         
            +
             
     | 
| 
      
 182 
     | 
    
         
            +
                    def self.api_error(errors, rcode, rbody, error_obj)
         
     | 
| 
      
 183 
     | 
    
         
            +
                      Maestrano::API::Error::BaseError.new(errors[:message], rcode, rbody, error_obj)
         
     | 
| 
      
 184 
     | 
    
         
            +
                    end
         
     | 
| 
      
 185 
     | 
    
         
            +
             
     | 
| 
      
 186 
     | 
    
         
            +
                    def self.handle_restclient_error(e)
         
     | 
| 
      
 187 
     | 
    
         
            +
                      case e
         
     | 
| 
      
 188 
     | 
    
         
            +
                      when RestClient::ServerBrokeConnection, RestClient::RequestTimeout
         
     | 
| 
      
 189 
     | 
    
         
            +
                        message = "Could not connect to Maestrano. " +
         
     | 
| 
      
 190 
     | 
    
         
            +
                          "Please check your internet connection and try again. " +
         
     | 
| 
      
 191 
     | 
    
         
            +
                          "If this problem persists, you should check Maestrano service status at " +
         
     | 
| 
      
 192 
     | 
    
         
            +
                          "https://twitter.com/maestrano, or let us know at support@maestrano.com."
         
     | 
| 
      
 193 
     | 
    
         
            +
             
     | 
| 
      
 194 
     | 
    
         
            +
                      when RestClient::SSLCertificateNotVerified
         
     | 
| 
      
 195 
     | 
    
         
            +
                        message = "Could not verify Maestrano's SSL certificate. " +
         
     | 
| 
      
 196 
     | 
    
         
            +
                          "Please make sure that your network is not intercepting certificates. " +
         
     | 
| 
      
 197 
     | 
    
         
            +
                          "(Try going to https://maestrano.com/api/v1/ping in your browser.) " +
         
     | 
| 
      
 198 
     | 
    
         
            +
                          "If this problem persists, let us know at support@maestrano.com."
         
     | 
| 
      
 199 
     | 
    
         
            +
             
     | 
| 
      
 200 
     | 
    
         
            +
                      when SocketError
         
     | 
| 
      
 201 
     | 
    
         
            +
                        message = "Unexpected error communicating when trying to connect to Maestrano. " +
         
     | 
| 
      
 202 
     | 
    
         
            +
                          "You may be seeing this message because your DNS is not working. " +
         
     | 
| 
      
 203 
     | 
    
         
            +
                          "To check, try running 'host maestrano.com' from the command line."
         
     | 
| 
      
 204 
     | 
    
         
            +
             
     | 
| 
      
 205 
     | 
    
         
            +
                      else
         
     | 
| 
      
 206 
     | 
    
         
            +
                        message = "Unexpected error communicating with Maestrano. " +
         
     | 
| 
      
 207 
     | 
    
         
            +
                          "If this problem persists, let us know at support@maestrano.com."
         
     | 
| 
      
 208 
     | 
    
         
            +
             
     | 
| 
      
 209 
     | 
    
         
            +
                      end
         
     | 
| 
      
 210 
     | 
    
         
            +
             
     | 
| 
      
 211 
     | 
    
         
            +
                      raise APIConnectionError.new(message + "\n\n(Network error: #{e.message})")
         
     | 
| 
      
 212 
     | 
    
         
            +
                    end
         
     | 
| 
      
 213 
     | 
    
         
            +
                  end
         
     | 
| 
      
 214 
     | 
    
         
            +
                end
         
     | 
| 
      
 215 
     | 
    
         
            +
              end
         
     | 
| 
      
 216 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,18 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Maestrano
         
     | 
| 
      
 2 
     | 
    
         
            +
              module API
         
     | 
| 
      
 3 
     | 
    
         
            +
                module Operation
         
     | 
| 
      
 4 
     | 
    
         
            +
                  module Create
         
     | 
| 
      
 5 
     | 
    
         
            +
                    module ClassMethods
         
     | 
| 
      
 6 
     | 
    
         
            +
                      def create(params={}, api_key=nil)
         
     | 
| 
      
 7 
     | 
    
         
            +
                        response, api_key = Maestrano::API::Operation::Base.request(:post, self.url, api_key, params)
         
     | 
| 
      
 8 
     | 
    
         
            +
                        Util.convert_to_maestrano_object(response, api_key)
         
     | 
| 
      
 9 
     | 
    
         
            +
                      end
         
     | 
| 
      
 10 
     | 
    
         
            +
                    end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                    def self.included(base)
         
     | 
| 
      
 13 
     | 
    
         
            +
                      base.extend(ClassMethods)
         
     | 
| 
      
 14 
     | 
    
         
            +
                    end
         
     | 
| 
      
 15 
     | 
    
         
            +
                  end
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
              end
         
     | 
| 
      
 18 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,13 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Maestrano
         
     | 
| 
      
 2 
     | 
    
         
            +
              module API
         
     | 
| 
      
 3 
     | 
    
         
            +
                module Operation
         
     | 
| 
      
 4 
     | 
    
         
            +
                  module Delete
         
     | 
| 
      
 5 
     | 
    
         
            +
                    def delete(params = {})
         
     | 
| 
      
 6 
     | 
    
         
            +
                      response, api_key = Maestrano::API::Operation::Base.request(:delete, url, @api_key, params)
         
     | 
| 
      
 7 
     | 
    
         
            +
                      refresh_from(response, api_key)
         
     | 
| 
      
 8 
     | 
    
         
            +
                      self
         
     | 
| 
      
 9 
     | 
    
         
            +
                    end
         
     | 
| 
      
 10 
     | 
    
         
            +
                  end
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
              end
         
     | 
| 
      
 13 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,18 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Maestrano
         
     | 
| 
      
 2 
     | 
    
         
            +
              module API
         
     | 
| 
      
 3 
     | 
    
         
            +
                module Operation
         
     | 
| 
      
 4 
     | 
    
         
            +
                  module List
         
     | 
| 
      
 5 
     | 
    
         
            +
                    module ClassMethods
         
     | 
| 
      
 6 
     | 
    
         
            +
                      def all(filters={}, api_key=nil)
         
     | 
| 
      
 7 
     | 
    
         
            +
                        response, api_key = Maestrano::API::Operation::Base.request(:get, url, api_key, filters)
         
     | 
| 
      
 8 
     | 
    
         
            +
                        Util.convert_to_maestrano_object(response, api_key)
         
     | 
| 
      
 9 
     | 
    
         
            +
                      end
         
     | 
| 
      
 10 
     | 
    
         
            +
                    end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                    def self.included(base)
         
     | 
| 
      
 13 
     | 
    
         
            +
                      base.extend(ClassMethods)
         
     | 
| 
      
 14 
     | 
    
         
            +
                    end
         
     | 
| 
      
 15 
     | 
    
         
            +
                  end
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
              end
         
     | 
| 
      
 18 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,59 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Maestrano
         
     | 
| 
      
 2 
     | 
    
         
            +
              module API
         
     | 
| 
      
 3 
     | 
    
         
            +
                module Operation
         
     | 
| 
      
 4 
     | 
    
         
            +
                  module Update
         
     | 
| 
      
 5 
     | 
    
         
            +
                    def save(opts={})
         
     | 
| 
      
 6 
     | 
    
         
            +
                      values = serialize_params(self).merge(opts)
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                      if @values[:metadata]
         
     | 
| 
      
 9 
     | 
    
         
            +
                        values[:metadata] = serialize_metadata
         
     | 
| 
      
 10 
     | 
    
         
            +
                      end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                      if values.length > 0
         
     | 
| 
      
 13 
     | 
    
         
            +
                        values.delete(:id)
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                        response, api_key = Maestrano::API::Operation::Base.request(:put, url, @api_key, values)
         
     | 
| 
      
 16 
     | 
    
         
            +
                        refresh_from(response, api_key)
         
     | 
| 
      
 17 
     | 
    
         
            +
                      end
         
     | 
| 
      
 18 
     | 
    
         
            +
                      self
         
     | 
| 
      
 19 
     | 
    
         
            +
                    end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                    def serialize_metadata
         
     | 
| 
      
 22 
     | 
    
         
            +
                      if @unsaved_values.include?(:metadata)
         
     | 
| 
      
 23 
     | 
    
         
            +
                        # the metadata object has been reassigned
         
     | 
| 
      
 24 
     | 
    
         
            +
                        # i.e. as object.metadata = {key => val}
         
     | 
| 
      
 25 
     | 
    
         
            +
                        metadata_update = @values[:metadata]  # new hash
         
     | 
| 
      
 26 
     | 
    
         
            +
                        new_keys = metadata_update.keys.map(&:to_sym)
         
     | 
| 
      
 27 
     | 
    
         
            +
                        # remove keys at the server, but not known locally
         
     | 
| 
      
 28 
     | 
    
         
            +
                        keys_to_unset = @previous_metadata.keys - new_keys
         
     | 
| 
      
 29 
     | 
    
         
            +
                        keys_to_unset.each {|key| metadata_update[key] = ''}
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                        metadata_update
         
     | 
| 
      
 32 
     | 
    
         
            +
                      else
         
     | 
| 
      
 33 
     | 
    
         
            +
                        # metadata is a Maestrano::API::Object, and can be serialized normally
         
     | 
| 
      
 34 
     | 
    
         
            +
                        serialize_params(@values[:metadata])
         
     | 
| 
      
 35 
     | 
    
         
            +
                      end
         
     | 
| 
      
 36 
     | 
    
         
            +
                    end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                    def serialize_params(obj)
         
     | 
| 
      
 39 
     | 
    
         
            +
                      case obj
         
     | 
| 
      
 40 
     | 
    
         
            +
                      when nil
         
     | 
| 
      
 41 
     | 
    
         
            +
                        ''
         
     | 
| 
      
 42 
     | 
    
         
            +
                      when Maestrano::API::Object
         
     | 
| 
      
 43 
     | 
    
         
            +
                        unsaved_keys = obj.instance_variable_get(:@unsaved_values)
         
     | 
| 
      
 44 
     | 
    
         
            +
                        obj_values = obj.instance_variable_get(:@values)
         
     | 
| 
      
 45 
     | 
    
         
            +
                        update_hash = {}
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                        unsaved_keys.each do |k|
         
     | 
| 
      
 48 
     | 
    
         
            +
                          update_hash[k] = serialize_params(obj_values[k])
         
     | 
| 
      
 49 
     | 
    
         
            +
                        end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                        update_hash
         
     | 
| 
      
 52 
     | 
    
         
            +
                      else
         
     | 
| 
      
 53 
     | 
    
         
            +
                        obj
         
     | 
| 
      
 54 
     | 
    
         
            +
                      end
         
     | 
| 
      
 55 
     | 
    
         
            +
                    end
         
     | 
| 
      
 56 
     | 
    
         
            +
                  end
         
     | 
| 
      
 57 
     | 
    
         
            +
                end
         
     | 
| 
      
 58 
     | 
    
         
            +
              end
         
     | 
| 
      
 59 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,39 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Maestrano
         
     | 
| 
      
 2 
     | 
    
         
            +
              module API
         
     | 
| 
      
 3 
     | 
    
         
            +
                class Resource < Maestrano::API::Object
         
     | 
| 
      
 4 
     | 
    
         
            +
                  def self.class_name
         
     | 
| 
      
 5 
     | 
    
         
            +
                    self.name.split('::').reject { |w| w.to_s == "Maestrano" }
         
     | 
| 
      
 6 
     | 
    
         
            +
                  end
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  def self.url
         
     | 
| 
      
 9 
     | 
    
         
            +
                    if self == Maestrano::API::Resource
         
     | 
| 
      
 10 
     | 
    
         
            +
                      raise NotImplementedError.new('Maestrano::API::Resource is an abstract class.  You should perform actions on its subclasses (Bill, Customer, etc.)')
         
     | 
| 
      
 11 
     | 
    
         
            +
                    end
         
     | 
| 
      
 12 
     | 
    
         
            +
                    if class_name.is_a?(Array)
         
     | 
| 
      
 13 
     | 
    
         
            +
                      class_name.map { |w| CGI.escape(w.downcase) }.join("/") + 's'
         
     | 
| 
      
 14 
     | 
    
         
            +
                    else
         
     | 
| 
      
 15 
     | 
    
         
            +
                      "#{CGI.escape(class_name.downcase)}s"
         
     | 
| 
      
 16 
     | 
    
         
            +
                    end
         
     | 
| 
      
 17 
     | 
    
         
            +
                  end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                  def url
         
     | 
| 
      
 20 
     | 
    
         
            +
                    unless id = self['id']
         
     | 
| 
      
 21 
     | 
    
         
            +
                      raise Maestrano::API::Error::InvalidRequestError.new("Could not determine which URL to request: #{self.class} instance has invalid ID: #{id.inspect}", 'id')
         
     | 
| 
      
 22 
     | 
    
         
            +
                    end
         
     | 
| 
      
 23 
     | 
    
         
            +
                    "#{self.class.url}/#{CGI.escape(id)}"
         
     | 
| 
      
 24 
     | 
    
         
            +
                  end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                  def refresh
         
     | 
| 
      
 27 
     | 
    
         
            +
                    response, api_key = Maestrano::API::Operation::Base.request(:get, url, @api_key, @retrieve_options)
         
     | 
| 
      
 28 
     | 
    
         
            +
                    refresh_from(response, api_key)
         
     | 
| 
      
 29 
     | 
    
         
            +
                    self
         
     | 
| 
      
 30 
     | 
    
         
            +
                  end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                  def self.retrieve(id, api_key=nil)
         
     | 
| 
      
 33 
     | 
    
         
            +
                    instance = self.new(id, api_key)
         
     | 
| 
      
 34 
     | 
    
         
            +
                    instance.refresh
         
     | 
| 
      
 35 
     | 
    
         
            +
                    instance
         
     | 
| 
      
 36 
     | 
    
         
            +
                  end
         
     | 
| 
      
 37 
     | 
    
         
            +
                end
         
     | 
| 
      
 38 
     | 
    
         
            +
              end
         
     | 
| 
      
 39 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,121 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Maestrano
         
     | 
| 
      
 2 
     | 
    
         
            +
              module API
         
     | 
| 
      
 3 
     | 
    
         
            +
                module Util
         
     | 
| 
      
 4 
     | 
    
         
            +
                  def self.objects_to_ids(h)
         
     | 
| 
      
 5 
     | 
    
         
            +
                    case h
         
     | 
| 
      
 6 
     | 
    
         
            +
                    when Maestrano::API::Resource
         
     | 
| 
      
 7 
     | 
    
         
            +
                      h.id
         
     | 
| 
      
 8 
     | 
    
         
            +
                    when Hash
         
     | 
| 
      
 9 
     | 
    
         
            +
                      res = {}
         
     | 
| 
      
 10 
     | 
    
         
            +
                      h.each { |k, v| res[k] = objects_to_ids(v) unless v.nil? }
         
     | 
| 
      
 11 
     | 
    
         
            +
                      res
         
     | 
| 
      
 12 
     | 
    
         
            +
                    when Array
         
     | 
| 
      
 13 
     | 
    
         
            +
                      h.map { |v| objects_to_ids(v) }
         
     | 
| 
      
 14 
     | 
    
         
            +
                    when Time
         
     | 
| 
      
 15 
     | 
    
         
            +
                      h.iso8601
         
     | 
| 
      
 16 
     | 
    
         
            +
                    else
         
     | 
| 
      
 17 
     | 
    
         
            +
                      h
         
     | 
| 
      
 18 
     | 
    
         
            +
                    end
         
     | 
| 
      
 19 
     | 
    
         
            +
                  end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                  def self.object_classes
         
     | 
| 
      
 22 
     | 
    
         
            +
                    @object_classes ||= {
         
     | 
| 
      
 23 
     | 
    
         
            +
                      'account_bill' => Maestrano::Account::Bill,
         
     | 
| 
      
 24 
     | 
    
         
            +
                      'internal_list_object' => Maestrano::API::ListObject
         
     | 
| 
      
 25 
     | 
    
         
            +
                    }
         
     | 
| 
      
 26 
     | 
    
         
            +
                  end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                  def self.convert_to_maestrano_object(resp, api_key)
         
     | 
| 
      
 29 
     | 
    
         
            +
                    case resp
         
     | 
| 
      
 30 
     | 
    
         
            +
                    when Array
         
     | 
| 
      
 31 
     | 
    
         
            +
                      if resp.empty? || !resp.first[:object]
         
     | 
| 
      
 32 
     | 
    
         
            +
                        resp
         
     | 
| 
      
 33 
     | 
    
         
            +
                      else
         
     | 
| 
      
 34 
     | 
    
         
            +
                        list = convert_to_maestrano_object({
         
     | 
| 
      
 35 
     | 
    
         
            +
                          object: 'internal_list_object', 
         
     | 
| 
      
 36 
     | 
    
         
            +
                          data:[],
         
     | 
| 
      
 37 
     | 
    
         
            +
                          url: convert_to_maestrano_object(resp.first, api_key).class.url
         
     | 
| 
      
 38 
     | 
    
         
            +
                        },api_key)
         
     | 
| 
      
 39 
     | 
    
         
            +
                        
         
     | 
| 
      
 40 
     | 
    
         
            +
                        resp.each do |i|
         
     | 
| 
      
 41 
     | 
    
         
            +
                          list.data.push(convert_to_maestrano_object(i, api_key))
         
     | 
| 
      
 42 
     | 
    
         
            +
                        end
         
     | 
| 
      
 43 
     | 
    
         
            +
                        list
         
     | 
| 
      
 44 
     | 
    
         
            +
                      end
         
     | 
| 
      
 45 
     | 
    
         
            +
                    when Hash
         
     | 
| 
      
 46 
     | 
    
         
            +
                      # Try converting to a known object class.  If none available, fall back to generic Maestrano::API::Object
         
     | 
| 
      
 47 
     | 
    
         
            +
                      object_classes.fetch(resp[:object], Maestrano::API::Object).construct_from(resp, api_key)
         
     | 
| 
      
 48 
     | 
    
         
            +
                    else
         
     | 
| 
      
 49 
     | 
    
         
            +
                      # Automatically parse iso8601 dates
         
     | 
| 
      
 50 
     | 
    
         
            +
                      if resp =~ /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/
         
     | 
| 
      
 51 
     | 
    
         
            +
                        Time.iso8601(resp).utc
         
     | 
| 
      
 52 
     | 
    
         
            +
                      else
         
     | 
| 
      
 53 
     | 
    
         
            +
                        resp
         
     | 
| 
      
 54 
     | 
    
         
            +
                      end
         
     | 
| 
      
 55 
     | 
    
         
            +
                    end
         
     | 
| 
      
 56 
     | 
    
         
            +
                  end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                  def self.file_readable(file)
         
     | 
| 
      
 59 
     | 
    
         
            +
                    # This is nominally equivalent to File.readable?, but that can
         
     | 
| 
      
 60 
     | 
    
         
            +
                    # report incorrect results on some more oddball filesystems
         
     | 
| 
      
 61 
     | 
    
         
            +
                    # (such as AFS)
         
     | 
| 
      
 62 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 63 
     | 
    
         
            +
                      File.open(file) { |f| }
         
     | 
| 
      
 64 
     | 
    
         
            +
                    rescue
         
     | 
| 
      
 65 
     | 
    
         
            +
                      false
         
     | 
| 
      
 66 
     | 
    
         
            +
                    else
         
     | 
| 
      
 67 
     | 
    
         
            +
                      true
         
     | 
| 
      
 68 
     | 
    
         
            +
                    end
         
     | 
| 
      
 69 
     | 
    
         
            +
                  end
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                  def self.symbolize_names(object)
         
     | 
| 
      
 72 
     | 
    
         
            +
                    case object
         
     | 
| 
      
 73 
     | 
    
         
            +
                    when Hash
         
     | 
| 
      
 74 
     | 
    
         
            +
                      new = {}
         
     | 
| 
      
 75 
     | 
    
         
            +
                      object.each do |key, value|
         
     | 
| 
      
 76 
     | 
    
         
            +
                        key = (key.to_sym rescue key) || key
         
     | 
| 
      
 77 
     | 
    
         
            +
                        new[key] = symbolize_names(value)
         
     | 
| 
      
 78 
     | 
    
         
            +
                      end
         
     | 
| 
      
 79 
     | 
    
         
            +
                      new
         
     | 
| 
      
 80 
     | 
    
         
            +
                    when Array
         
     | 
| 
      
 81 
     | 
    
         
            +
                      object.map { |value| symbolize_names(value) }
         
     | 
| 
      
 82 
     | 
    
         
            +
                    else
         
     | 
| 
      
 83 
     | 
    
         
            +
                      object
         
     | 
| 
      
 84 
     | 
    
         
            +
                    end
         
     | 
| 
      
 85 
     | 
    
         
            +
                  end
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
                  def self.url_encode(key)
         
     | 
| 
      
 88 
     | 
    
         
            +
                    URI.escape(key.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
         
     | 
| 
      
 89 
     | 
    
         
            +
                  end
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
                  def self.flatten_params(params, parent_key=nil)
         
     | 
| 
      
 92 
     | 
    
         
            +
                    result = []
         
     | 
| 
      
 93 
     | 
    
         
            +
                    params.each do |key, value|
         
     | 
| 
      
 94 
     | 
    
         
            +
                      calculated_key = parent_key ? "#{parent_key}[#{url_encode(key)}]" : url_encode(key)
         
     | 
| 
      
 95 
     | 
    
         
            +
                      if value.is_a?(Hash)
         
     | 
| 
      
 96 
     | 
    
         
            +
                        result += flatten_params(value, calculated_key)
         
     | 
| 
      
 97 
     | 
    
         
            +
                      elsif value.is_a?(Array)
         
     | 
| 
      
 98 
     | 
    
         
            +
                        result += flatten_params_array(value, calculated_key)
         
     | 
| 
      
 99 
     | 
    
         
            +
                      else
         
     | 
| 
      
 100 
     | 
    
         
            +
                        result << [calculated_key, value]
         
     | 
| 
      
 101 
     | 
    
         
            +
                      end
         
     | 
| 
      
 102 
     | 
    
         
            +
                    end
         
     | 
| 
      
 103 
     | 
    
         
            +
                    result
         
     | 
| 
      
 104 
     | 
    
         
            +
                  end
         
     | 
| 
      
 105 
     | 
    
         
            +
             
     | 
| 
      
 106 
     | 
    
         
            +
                  def self.flatten_params_array(value, calculated_key)
         
     | 
| 
      
 107 
     | 
    
         
            +
                    result = []
         
     | 
| 
      
 108 
     | 
    
         
            +
                    value.each do |elem|
         
     | 
| 
      
 109 
     | 
    
         
            +
                      if elem.is_a?(Hash)
         
     | 
| 
      
 110 
     | 
    
         
            +
                        result += flatten_params(elem, calculated_key)
         
     | 
| 
      
 111 
     | 
    
         
            +
                      elsif elem.is_a?(Array)
         
     | 
| 
      
 112 
     | 
    
         
            +
                        result += flatten_params_array(elem, calculated_key)
         
     | 
| 
      
 113 
     | 
    
         
            +
                      else
         
     | 
| 
      
 114 
     | 
    
         
            +
                        result << ["#{calculated_key}[]", elem]
         
     | 
| 
      
 115 
     | 
    
         
            +
                      end
         
     | 
| 
      
 116 
     | 
    
         
            +
                    end
         
     | 
| 
      
 117 
     | 
    
         
            +
                    result
         
     | 
| 
      
 118 
     | 
    
         
            +
                  end
         
     | 
| 
      
 119 
     | 
    
         
            +
                end
         
     | 
| 
      
 120 
     | 
    
         
            +
              end
         
     | 
| 
      
 121 
     | 
    
         
            +
            end
         
     |