sfdc 0.0.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +8 -8
  2. data/lib/sfdc.rb +39 -1
  3. data/lib/sfdc/attachment.rb +23 -0
  4. data/lib/sfdc/chatter/comment.rb +10 -0
  5. data/lib/sfdc/chatter/conversation.rb +100 -0
  6. data/lib/sfdc/chatter/feed.rb +64 -0
  7. data/lib/sfdc/chatter/feed_item.rb +40 -0
  8. data/lib/sfdc/chatter/feeds.rb +5 -0
  9. data/lib/sfdc/chatter/filter_feed.rb +14 -0
  10. data/lib/sfdc/chatter/group.rb +45 -0
  11. data/lib/sfdc/chatter/group_membership.rb +9 -0
  12. data/lib/sfdc/chatter/like.rb +9 -0
  13. data/lib/sfdc/chatter/message.rb +29 -0
  14. data/lib/sfdc/chatter/photo_methods.rb +55 -0
  15. data/lib/sfdc/chatter/record.rb +122 -0
  16. data/lib/sfdc/chatter/subscription.rb +9 -0
  17. data/lib/sfdc/chatter/user.rb +153 -0
  18. data/lib/sfdc/client.rb +98 -0
  19. data/lib/sfdc/client/api.rb +320 -0
  20. data/lib/sfdc/client/authentication.rb +40 -0
  21. data/lib/sfdc/client/caching.rb +26 -0
  22. data/lib/sfdc/client/canvas.rb +12 -0
  23. data/lib/sfdc/client/connection.rb +74 -0
  24. data/lib/sfdc/client/picklists.rb +90 -0
  25. data/lib/sfdc/client/streaming.rb +31 -0
  26. data/lib/sfdc/client/verbs.rb +68 -0
  27. data/lib/sfdc/collection.rb +40 -0
  28. data/lib/sfdc/config.rb +136 -0
  29. data/lib/sfdc/mash.rb +65 -0
  30. data/lib/sfdc/middleware.rb +29 -0
  31. data/lib/sfdc/middleware/authentication.rb +63 -0
  32. data/lib/sfdc/middleware/authentication/password.rb +20 -0
  33. data/lib/sfdc/middleware/authentication/token.rb +15 -0
  34. data/lib/sfdc/middleware/authorization.rb +19 -0
  35. data/lib/sfdc/middleware/caching.rb +24 -0
  36. data/lib/sfdc/middleware/gzip.rb +31 -0
  37. data/lib/sfdc/middleware/instance_url.rb +18 -0
  38. data/lib/sfdc/middleware/logger.rb +40 -0
  39. data/lib/sfdc/middleware/mashify.rb +18 -0
  40. data/lib/sfdc/middleware/multipart.rb +53 -0
  41. data/lib/sfdc/middleware/raise_error.rb +23 -0
  42. data/lib/sfdc/signed_request.rb +48 -0
  43. data/lib/sfdc/sobject.rb +64 -0
  44. data/lib/sfdc/upload_io.rb +20 -0
  45. data/lib/sfdc/version.rb +1 -1
  46. metadata +59 -4
  47. data/lib/sfdc/sfdc.rb +0 -0
data/lib/sfdc/mash.rb ADDED
@@ -0,0 +1,65 @@
1
+ require 'hashie/mash'
2
+
3
+ module Sfdc
4
+ class Mash < Hashie::Mash
5
+
6
+ class << self
7
+
8
+ # Pass in an Array or Hash and it will be recursively converted into the
9
+ # appropriate Sfdc::Collection, Sfdc::SObject and
10
+ # Sfdc::Mash objects.
11
+ def build(val, client)
12
+ if val.is_a?(Array)
13
+ val.collect { |val| self.build(val, client) }
14
+ elsif val.is_a?(Hash)
15
+ self.klass(val).new(val, client)
16
+ else
17
+ val
18
+ end
19
+ end
20
+
21
+ # When passed a hash, it will determine what class is appropriate to
22
+ # represent the data.
23
+ def klass(val)
24
+ if val.has_key? 'records'
25
+ # When the hash has a records key, it should be considered a collection
26
+ # of sobject records.
27
+ Sfdc::Collection
28
+ elsif val.has_key? 'attributes'
29
+ if val['attributes']['type'] == 'Attachment'
30
+ Sfdc::Attachment
31
+ else
32
+ # When the hash contains an attributes key, it should be considered an
33
+ # sobject record
34
+ Sfdc::SObject
35
+ end
36
+ else
37
+ # Fallback to a standard Sfdc::Mash for everything else
38
+ Sfdc::Mash
39
+ end
40
+ end
41
+
42
+ end
43
+
44
+ def initialize(source_hash = nil, client = nil, default = nil, &blk)
45
+ @client = client
46
+ deep_update(source_hash) if source_hash
47
+ default ? super(default) : super(&blk)
48
+ end
49
+
50
+ def convert_value(val, duping=false)
51
+ case val
52
+ when self.class
53
+ val.dup
54
+ when ::Hash
55
+ val = val.dup if duping
56
+ self.class.klass(val).new(val, @client)
57
+ when Array
58
+ val.collect{ |e| convert_value(e) }
59
+ else
60
+ val
61
+ end
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,29 @@
1
+ module Sfdc
2
+ # Base class that all middleware can extend. Provides some convenient helper
3
+ # functions.
4
+ class Middleware < Faraday::Middleware
5
+ autoload :RaiseError, 'sfdc/middleware/raise_error'
6
+ autoload :Authentication, 'sfdc/middleware/authentication'
7
+ autoload :Authorization, 'sfdc/middleware/authorization'
8
+ autoload :InstanceURL, 'sfdc/middleware/instance_url'
9
+ autoload :Multipart, 'sfdc/middleware/multipart'
10
+ autoload :Mashify, 'sfdc/middleware/mashify'
11
+ autoload :Caching, 'sfdc/middleware/caching'
12
+ autoload :Logger, 'sfdc/middleware/logger'
13
+ autoload :Gzip, 'sfdc/middleware/gzip'
14
+
15
+ def initialize(app, client, options)
16
+ @app, @client, @options = app, client, options
17
+ end
18
+
19
+ # Internal: Proxy to the client.
20
+ def client
21
+ @client
22
+ end
23
+
24
+ # Internal: Proxy to the client's faraday connection.
25
+ def connection
26
+ client.send(:connection)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,63 @@
1
+ module Sfdc
2
+ # Faraday middleware that allows for on the fly authentication of requests.
3
+ # When a request fails (a status of 401 is returned), the middleware
4
+ # will attempt to either reauthenticate (username and password) or refresh
5
+ # the oauth access token (if a refresh token is present).
6
+ class Middleware::Authentication < Sfdc::Middleware
7
+ autoload :Password, 'sfdc/middleware/authentication/password'
8
+ autoload :Token, 'sfdc/middleware/authentication/token'
9
+
10
+ # Rescue from 401's, authenticate then raise the error again so the client
11
+ # can reissue the request.
12
+ def call(env)
13
+ @app.call(env)
14
+ rescue Sfdc::UnauthorizedError
15
+ authenticate!
16
+ raise
17
+ end
18
+
19
+ # Internal: Performs the authentication and returns the response body.
20
+ def authenticate!
21
+ response = connection.post '/services/oauth2/token' do |req|
22
+ req.body = encode_www_form(params)
23
+ end
24
+ raise Sfdc::AuthenticationError, error_message(response) if response.status != 200
25
+ @options[:instance_url] = response.body['instance_url']
26
+ @options[:oauth_token] = response.body['access_token']
27
+ response.body
28
+ end
29
+
30
+ # Internal: The params to post to the OAuth service.
31
+ def params
32
+ raise 'not implemented'
33
+ end
34
+
35
+ # Internal: Faraday connection to use when sending an authentication request.
36
+ def connection
37
+ @connection ||= Faraday.new(:url => "https://#{@options[:host]}") do |builder|
38
+ builder.use Sfdc::Middleware::Mashify, nil, @options
39
+ builder.response :json
40
+ builder.use Sfdc::Middleware::Logger, Sfdc.configuration.logger, @options if Sfdc.log?
41
+ builder.adapter Faraday.default_adapter
42
+ end
43
+ end
44
+
45
+ # Internal: The parsed error response.
46
+ def error_message(response)
47
+ "#{response.body['error']}: #{response.body['error_description']}"
48
+ end
49
+
50
+ # Featured detect form encoding.
51
+ # URI in 1.8 does not include encode_www_form
52
+ def encode_www_form(params)
53
+ if URI.respond_to?(:encode_www_form)
54
+ URI.encode_www_form(params)
55
+ else
56
+ params.map do |k, v|
57
+ k, v = CGI.escape(k.to_s), CGI.escape(v.to_s)
58
+ "#{k}=#{v}"
59
+ end.join('&')
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,20 @@
1
+ module Sfdc
2
+
3
+ # Authentication middleware used if username and password flow is used
4
+ class Middleware::Authentication::Password < Sfdc::Middleware::Authentication
5
+
6
+ def params
7
+ { :grant_type => 'password',
8
+ :client_id => @options[:client_id],
9
+ :client_secret => @options[:client_secret],
10
+ :username => @options[:username],
11
+ :password => password }
12
+ end
13
+
14
+ def password
15
+ "#{@options[:password]}#{@options[:security_token]}"
16
+ end
17
+
18
+ end
19
+
20
+ end
@@ -0,0 +1,15 @@
1
+ module Sfdc
2
+
3
+ # Authentication middleware used if oauth_token and refresh_token are set
4
+ class Middleware::Authentication::Token < Sfdc::Middleware::Authentication
5
+
6
+ def params
7
+ { :grant_type => 'refresh_token',
8
+ :refresh_token => @options[:refresh_token],
9
+ :client_id => @options[:client_id],
10
+ :client_secret => @options[:client_secret] }
11
+ end
12
+
13
+ end
14
+
15
+ end
@@ -0,0 +1,19 @@
1
+ module Sfdc
2
+
3
+ # Piece of middleware that simply injects the OAuth token into the request
4
+ # headers.
5
+ class Middleware::Authorization < Sfdc::Middleware
6
+ AUTH_HEADER = 'Authorization'.freeze
7
+
8
+ def call(env)
9
+ env[:request_headers][AUTH_HEADER] = %(OAuth #{token})
10
+ @app.call(env)
11
+ end
12
+
13
+ def token
14
+ @options[:oauth_token]
15
+ end
16
+
17
+ end
18
+
19
+ end
@@ -0,0 +1,24 @@
1
+ module Sfdc
2
+ class Middleware::Caching < FaradayMiddleware::Caching
3
+
4
+ def call(env)
5
+ expire(cache_key(env)) unless use_cache?
6
+ super
7
+ end
8
+
9
+ def expire(key)
10
+ cache.delete(key) if cache
11
+ end
12
+
13
+ # We don't want to cache requests for different clients, so append the
14
+ # oauth token to the cache key.
15
+ def cache_key(env)
16
+ super(env) + env[:request_headers][Sfdc::Middleware::Authorization::AUTH_HEADER].gsub(/\s/, '')
17
+ end
18
+
19
+ def use_cache?
20
+ !@options.has_key?(:use_cache) || @options[:use_cache]
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,31 @@
1
+ require 'zlib'
2
+
3
+ module Sfdc
4
+ # Middleware to uncompress GZIP compressed responses from Salesforce.
5
+ class Middleware::Gzip < Sfdc::Middleware
6
+ ACCEPT_ENCODING_HEADER = 'Accept-Encoding'.freeze
7
+ CONTENT_ENCODING_HEADER = 'Content-Encoding'.freeze
8
+ ENCODING = 'gzip'.freeze
9
+
10
+ def call(env)
11
+ env[:request_headers][ACCEPT_ENCODING_HEADER] = ENCODING if @options[:compress]
12
+ @app.call(env).on_complete do |environment|
13
+ on_complete(environment)
14
+ end
15
+ end
16
+
17
+ def on_complete(env)
18
+ env[:body] = decompress(env[:body]) if gzipped?(env)
19
+ end
20
+
21
+ # Internal: Returns true if the response is gzipped.
22
+ def gzipped?(env)
23
+ env[:response_headers][CONTENT_ENCODING_HEADER] == ENCODING
24
+ end
25
+
26
+ # Internal: Decompresses a gzipped string.
27
+ def decompress(body)
28
+ Zlib::GzipReader.new(StringIO.new(body)).read
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,18 @@
1
+ module Sfdc
2
+
3
+ # Middleware which asserts that the instance_url is always set
4
+ class Middleware::InstanceURL < Sfdc::Middleware
5
+
6
+ def call(env)
7
+ # If the connection url_prefix isn't set, we must not be authenticated.
8
+ raise Sfdc::UnauthorizedError, 'Connection prefix not set' unless url_prefix_set?
9
+ @app.call(env)
10
+ end
11
+
12
+ def url_prefix_set?
13
+ !!(connection.url_prefix && connection.url_prefix.host)
14
+ end
15
+
16
+ end
17
+
18
+ end
@@ -0,0 +1,40 @@
1
+ require 'forwardable'
2
+
3
+ module Sfdc
4
+ class Middleware::Logger < Faraday::Response::Middleware
5
+ extend Forwardable
6
+
7
+ def initialize(app, logger, options)
8
+ super(app)
9
+ @options = options
10
+ @logger = logger || begin
11
+ require 'logger'
12
+ ::Logger.new(STDOUT)
13
+ end
14
+ end
15
+
16
+ def_delegators :@logger, :debug, :info, :warn, :error, :fatal
17
+
18
+ def call(env)
19
+ debug('request') do
20
+ dump :url => env[:url].to_s,
21
+ :method => env[:method],
22
+ :headers => env[:request_headers],
23
+ :body => env[:body]
24
+ end
25
+ super
26
+ end
27
+
28
+ def on_complete(env)
29
+ debug('response') do
30
+ dump :status => env[:status].to_s,
31
+ :headers => env[:response_headers],
32
+ :body => env[:body]
33
+ end
34
+ end
35
+
36
+ def dump(hash)
37
+ "\n" + hash.map { |k, v| " #{k}: #{v.inspect}" }.join("\n")
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,18 @@
1
+ module Sfdc
2
+ # Middleware the converts sobject records from JSON into Sfdc::SObject objects
3
+ # and collections of records into Sfdc::Collection objects.
4
+ class Middleware::Mashify < Sfdc::Middleware
5
+
6
+ def call(env)
7
+ @env = env
8
+ response = @app.call(env)
9
+ env[:body] = Sfdc::Mash.build(body, client)
10
+ response
11
+ end
12
+
13
+ def body
14
+ @env[:body]
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,53 @@
1
+ module Sfdc
2
+ class Middleware::Multipart < Faraday::Request::UrlEncoded
3
+ self.mime_type = 'multipart/form-data'.freeze
4
+ DEFAULT_BOUNDARY = "--boundary_string".freeze
5
+
6
+ def call(env)
7
+ match_content_type(env) do |params|
8
+ env[:request] ||= {}
9
+ env[:request][:boundary] ||= DEFAULT_BOUNDARY
10
+ env[:request_headers][CONTENT_TYPE] += ";boundary=#{env[:request][:boundary]}"
11
+ env[:body] = create_multipart(env, params)
12
+ end
13
+ @app.call env
14
+ end
15
+
16
+ def process_request?(env)
17
+ type = request_type(env)
18
+ env[:body].respond_to?(:each_key) and !env[:body].empty? and (
19
+ (type.empty? and has_multipart?(env[:body])) or
20
+ type == self.class.mime_type
21
+ )
22
+ end
23
+
24
+ def has_multipart?(obj)
25
+ # string is an enum in 1.8, returning list of itself
26
+ if obj.respond_to?(:each) && !obj.is_a?(String)
27
+ (obj.respond_to?(:values) ? obj.values : obj).each do |val|
28
+ return true if (val.respond_to?(:content_type) || has_multipart?(val))
29
+ end
30
+ end
31
+ false
32
+ end
33
+
34
+ def create_multipart(env, params)
35
+ boundary = env[:request][:boundary]
36
+ parts = []
37
+
38
+ # Fields
39
+ parts << Faraday::Parts::Part.new(boundary, 'entity_content', params.reject { |k,v| v.respond_to? :content_type }.to_json)
40
+
41
+ # Files
42
+ params.each do |k,v|
43
+ parts << Faraday::Parts::Part.new(boundary, k.to_s, v) if v.respond_to? :content_type
44
+ end
45
+
46
+ parts << Faraday::Parts::EpiloguePart.new(boundary)
47
+
48
+ body = Faraday::CompositeReadIO.new(parts)
49
+ env[:request_headers]['Content-Length'] = body.length.to_s
50
+ return body
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,23 @@
1
+ module Sfdc
2
+ class Middleware::RaiseError < Faraday::Response::Middleware
3
+ def on_complete(env)
4
+ @env = env
5
+ case env[:status]
6
+ when 404
7
+ raise Faraday::Error::ResourceNotFound, message
8
+ when 401
9
+ raise Sfdc::UnauthorizedError, message
10
+ when 400...600
11
+ raise Faraday::Error::ClientError, message
12
+ end
13
+ end
14
+
15
+ def message
16
+ "#{body.first['errorCode']}: #{body.first['message']}"
17
+ end
18
+
19
+ def body
20
+ JSON.parse(@env[:body])
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,48 @@
1
+ require 'openssl'
2
+ require 'base64'
3
+ require 'json'
4
+
5
+ module Sfdc
6
+ class SignedRequest
7
+ # Public: Initializes and decodes the signed request
8
+ #
9
+ # signed_request - The POST message containing the signed request from Salesforce.
10
+ # client_secret - The oauth client secret used to encrypt the signed request.
11
+ #
12
+ # Returns the parsed JSON context.
13
+ def self.decode(signed_request, client_secret)
14
+ new(signed_request, client_secret).decode
15
+ end
16
+
17
+ def initialize(signed_request, client_secret)
18
+ @client_secret = client_secret
19
+ split_components(signed_request)
20
+ end
21
+
22
+ # Public: Decode the signed request.
23
+ #
24
+ # Returns the parsed JSON context.
25
+ # Returns nil if the signed request is invalid.
26
+ def decode
27
+ return nil if signature != hmac
28
+ JSON.parse(Base64.decode64(payload))
29
+ end
30
+
31
+ private
32
+ attr_reader :client_secret, :signature, :payload
33
+
34
+ def split_components(signed_request)
35
+ @signature, @payload = signed_request.split('.')
36
+ @signature = Base64.decode64(@signature)
37
+ end
38
+
39
+ def hmac
40
+ OpenSSL::HMAC.digest(digest, client_secret, payload)
41
+ end
42
+
43
+ def digest
44
+ OpenSSL::Digest::Digest.new('sha256')
45
+ end
46
+
47
+ end
48
+ end