Skip to content

Commit 6e30934

Browse files
author
Jared Pearson
authored
Add support for passing Salesforce OAuth access_token (#53)
1 parent 8de6325 commit 6e30934

21 files changed

+661
-551
lines changed

README.rdoc

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,17 @@ Add the following to your Gemfile
88

99
=== Authentication
1010

11-
The client will authenticate before performing other API calls, but you can manually authenticate as well
11+
In order to use this client, an access token retrieved from Salesforce OAuth must be specified. See
12+
the [authetication documentation on developer.pardot.com](https://developer.pardot.com/kb/authentication/) for more information.
1213

1314
require "ruby-pardot"
1415

15-
client = Pardot::Client.new email, password, user_key
16+
version = 3 # or 4
17+
access_token = '<access_token>' # Retrieve an access token from Salesforce using OAuth
18+
business_unit_id = '<business_unit_id>' # Specify the Business Unit ID of the account to access
19+
client = Pardot::Client.new nil, nil, nil, version, access_token, business_unit_id
1620

17-
# will raise a Pardot::ResponseError if login fails
18-
# will raise a Pardot::NetError if the http call fails
19-
client.authenticate
21+
Usage of the username, password, and API key authentication is deprecated and will be removed in an upcoming release.
2022

2123
=== Object Types
2224

lib/pardot/authentication.rb

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,26 @@
11
module Pardot
22
module Authentication
3-
3+
4+
# @deprecated Use of username and password authentication is deprecated.
45
def authenticate
6+
raise "Authentication not available when using Salesforce access token. See https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/intro_oauth_and_connected_apps.htm for more information." if using_salesforce_access_token?
7+
warn "[DEPRECATION] Use of username and password authentication is deprecated in favor of Salesforce OAuth. See https://developer.pardot.com/kb/authentication/ for more information."
58
resp = post "login", nil, nil, nil, :email => @email, :password => @password, :user_key => @user_key
69
update_version(resp["version"]) if resp && resp["version"]
710
@api_key = resp && resp["api_key"]
811
end
912

1013
def authenticated?
11-
@api_key != nil
14+
@api_key != nil || @salesforce_access_token != nil
15+
end
16+
17+
def using_salesforce_access_token?
18+
@salesforce_access_token != nil
1219
end
1320

1421
def reauthenticate
22+
raise "Reauthentication not available when using Salesforce access token. See https://developer.salesforce.com/docs/atlas.en-us.mobile_sdk.meta/mobile_sdk/oauth_refresh_token_flow.htm for more information." if using_salesforce_access_token?
23+
warn "[DEPRECATION] Use Salesforce OAuth to refresh the Salesforce access token. See https://developer.salesforce.com/docs/atlas.en-us.mobile_sdk.meta/mobile_sdk/oauth_refresh_token_flow.htm for more information."
1524
@api_key = nil
1625
authenticate
1726
end

lib/pardot/client.rb

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,23 @@ class Client
2121
include Objects::Visits
2222
include Objects::VisitorActivities
2323

24-
attr_accessor :email, :password, :user_key, :api_key, :version, :format
24+
attr_accessor :email, :password, :user_key, :api_key, :version, :salesforce_access_token, :business_unit_id, :format
25+
26+
# @deprecated Arguments email, password and user_key are deprecated. Use salesforce_access_token with Salesforce OAuth.
27+
def initialize email = nil, password = nil, user_key = nil, version = 3, salesforce_access_token = nil, business_unit_id = nil
28+
if !(email.nil? || password.nil? || user_key.nil?) then
29+
warn "[DEPRECATION] Use of username and password authentication is deprecated in favor of Salesforce OAuth. See https://developer.pardot.com/kb/authentication/ for more information."
30+
end
31+
32+
raise "business_unit_id required when using Salesforce access_token" if !salesforce_access_token.nil? && business_unit_id.nil?
33+
raise "Invalid business_unit_id value. Expected ID to start with '0Uv' and be length of 18 characters." if !business_unit_id.nil? && (!business_unit_id.start_with?('0Uv') || business_unit_id.length != 18)
2534

26-
def initialize email, password, user_key, version = 3
2735
@email = email
2836
@password = password
2937
@user_key = user_key
3038
@version = version
39+
@salesforce_access_token = salesforce_access_token
40+
@business_unit_id = business_unit_id
3141

3242
@format = "simple"
3343
end

lib/pardot/error.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ module Pardot
22
class Error < StandardError; end
33
class NetError < Error; end
44
class ExpiredApiKeyError < Error; end
5+
class AccessTokenExpiredError < Error; end
56

67
class ResponseError < Error
78
def initialize(res)

lib/pardot/http.rb

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ def post object, path, params = {}, num_retries = 0, bodyParams = {}
2929

3030
protected
3131

32+
# @deprecated With Salesforce OAuth, this method will never be invoked.
3233
def handle_expired_api_key method, object, path, params, num_retries, e
3334
raise e unless num_retries == 0
3435

@@ -46,7 +47,14 @@ def smooth_params object, params
4647

4748
def create_auth_header object
4849
return if object == "login"
49-
{ :Authorization => "Pardot api_key=#{@api_key}, user_key=#{@user_key}" }
50+
if using_salesforce_access_token? then
51+
{
52+
:Authorization => "Bearer #{@salesforce_access_token}",
53+
'Pardot-Business-Unit-Id' => @business_unit_id
54+
}
55+
else
56+
{ :Authorization => "Pardot api_key=#{@api_key}, user_key=#{@user_key}" }
57+
end
5058
end
5159

5260
def check_response http_response
@@ -56,6 +64,9 @@ def check_response http_response
5664
error ||= "Unknown Failure: #{rsp.inspect}" if rsp && rsp["stat"] == "fail"
5765
content = error['__content__'] if error.is_a?(Hash)
5866

67+
if [error, content].include?("access_token is invalid") && using_salesforce_access_token?
68+
raise AccessTokenExpiredError.new "Access token is invalid. Use Salesforce OAuth to refresh the existing Salesforce access token or to retrieve a new token. See https://developer.salesforce.com/docs/atlas.en-us.mobile_sdk.meta/mobile_sdk/oauth_refresh_token_flow.htm for more information."
69+
end
5970
if [error, content].include?("Invalid API key or user key") && @api_key
6071
raise ExpiredApiKeyError.new @api_key
6172
end

lib/pardot/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module Pardot
2-
VERSION = "1.3.2"
2+
VERSION = "1.4.0"
33
end

spec/pardot/authentication_spec.rb

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,32 @@
55
def create_client
66
@client = Pardot::Client.new "[email protected]", "foo", "bar"
77
end
8+
9+
def create_client_using_salesforce_access_token
10+
@client = Pardot::Client.new nil, nil, nil, 3, "access_token_value", '0Uv000000000001CAA'
11+
end
12+
13+
describe "authenticate with Salesforce access_token" do
14+
before do
15+
@client = create_client_using_salesforce_access_token
16+
end
17+
18+
it "raises error when calling authenticate" do
19+
expect{ @client.authenticate }.to raise_error.with_message(/Authentication not available when using Salesforce access token/)
20+
end
21+
22+
it "raises error when calling reauthenticate" do
23+
expect{ @client.reauthenticate }.to raise_error.with_message(/Reauthentication not available when using Salesforce access token/)
24+
end
25+
26+
it "returns true for authenticated" do
27+
expect(@client.authenticated?).to eq(true)
28+
end
29+
30+
it "returns true for using_salesforce_access_token" do
31+
expect(@client.using_salesforce_access_token?).to eq(true)
32+
end
33+
end
834

935
describe "authenticate" do
1036

@@ -45,6 +71,13 @@ def verifyBody
4571
verifyBody
4672
end
4773

74+
it "returns false for using_salesforce_access_token" do
75+
expect(@client.using_salesforce_access_token?).to eq(false)
76+
77+
authenticate
78+
expect(@client.using_salesforce_access_token?).to eq(false)
79+
verifyBody
80+
end
4881
end
4982

5083
describe "authenticateV4" do

spec/pardot/client_spec.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,33 @@
55
def create_client
66
@client = Pardot::Client.new "[email protected]", "foo", "bar"
77
end
8+
9+
describe "client with Salesforce access_token" do
10+
11+
it "should set properties" do
12+
@client = Pardot::Client.new nil, nil, nil, 3, 'access_token_value', '0Uv000000000001CAA'
13+
expect(@client.email).to eq(nil)
14+
expect(@client.password).to eq(nil)
15+
expect(@client.user_key).to eq(nil)
16+
expect(@client.api_key).to eq(nil)
17+
expect(@client.version).to eq(3)
18+
expect(@client.salesforce_access_token).to eq('access_token_value')
19+
expect(@client.business_unit_id).to eq('0Uv000000000001CAA')
20+
expect(@client.format).to eq("simple")
21+
end
22+
23+
it "raises error with nil business_unit_id" do
24+
expect{ Pardot::Client.new nil, nil, nil, 3, "access_token_value", nil }.to raise_error.with_message(/business_unit_id required when using Salesforce access_token/)
25+
end
26+
27+
it "raises error with invalid business_unit_id due to length" do
28+
expect{ Pardot::Client.new nil, nil, nil, 3, "access_token_value", '0Uv1234567890' }.to raise_error.with_message(/Invalid business_unit_id value. Expected ID to start with '0Uv' and be length of 18 characters./)
29+
end
30+
31+
it "raises error with invalid business_unit_id due to invalid prefix" do
32+
expect{ Pardot::Client.new nil, nil, nil, 3, "access_token_value", '001000000000001AAA' }.to raise_error.with_message(/Invalid business_unit_id value. Expected ID to start with '0Uv' and be length of 18 characters./)
33+
end
34+
end
835

936
describe "client" do
1037
after do

spec/pardot/http_spec.rb

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def get object = "foo", path = "/bar", params = {}
1919

2020
it "should notice errors and raise them as Pardot::ResponseError" do
2121
fake_get "/api/foo/version/3/bar?format=simple",
22-
%(?xml version="1.0" encoding="UTF-8"?>\n<rsp stat="fail" version="1.0">\n <err code="15">Login failed</err>\n</rsp>\n)
22+
%(<?xml version="1.0" encoding="UTF-8"?>\n<rsp stat="fail" version="1.0">\n <err code="15">Login failed</err>\n</rsp>\n)
2323

2424

2525
expect(lambda { get }).to raise_error(Pardot::ResponseError)
@@ -33,7 +33,7 @@ def get object = "foo", path = "/bar", params = {}
3333

3434
it "should call handle_expired_api_key when the api key expires" do
3535
fake_get "/api/foo/version/3/bar?format=simple",
36-
%(?xml version="1.0" encoding="UTF-8"?>\n<rsp stat="fail" version="1.0">\n <err code="15">Invalid API key or user key</err>\n</rsp>\n)
36+
%(<?xml version="1.0" encoding="UTF-8"?>\n<rsp stat="fail" version="1.0">\n <err code="15">Invalid API key or user key</err>\n</rsp>\n)
3737

3838
expect(@client).to receive(:handle_expired_api_key)
3939
get
@@ -49,7 +49,7 @@ def post object = "foo", path = "/bar", params = {}
4949

5050
it "should notice errors and raise them as Pardot::ResponseError" do
5151
fake_post "/api/foo/version/3/bar?format=simple",
52-
%(?xml version="1.0" encoding="UTF-8"?>\n<rsp stat="fail" version="1.0">\n <err code="15">Login failed</err>\n</rsp>\n)
52+
%(<?xml version="1.0" encoding="UTF-8"?>\n<rsp stat="fail" version="1.0">\n <err code="15">Login failed</err>\n</rsp>\n)
5353

5454
expect(lambda { post }).to raise_error(Pardot::ResponseError)
5555
end
@@ -62,7 +62,7 @@ def post object = "foo", path = "/bar", params = {}
6262

6363
it "should call handle_expired_api_key when the api key expires" do
6464
fake_post "/api/foo/version/3/bar?format=simple",
65-
%(?xml version="1.0" encoding="UTF-8"?>\n<rsp stat="fail" version="1.0">\n <err code="15">Invalid API key or user key</err>\n</rsp>\n)
65+
%(<?xml version="1.0" encoding="UTF-8"?>\n<rsp stat="fail" version="1.0">\n <err code="15">Invalid API key or user key</err>\n</rsp>\n)
6666

6767
expect(@client).to receive(:handle_expired_api_key)
6868
post
@@ -79,7 +79,7 @@ def get object = "foo", path = "/bar", params = {}
7979

8080
it "should notice errors and raise them as Pardot::ResponseError" do
8181
fake_get "/api/foo/version/4/bar?format=simple",
82-
%(?xml version="1.0" encoding="UTF-8"?>\n<rsp stat="fail" version="1.0">\n <err code="15">Login failed</err>\n</rsp>\n)
82+
%(<?xml version="1.0" encoding="UTF-8"?>\n<rsp stat="fail" version="1.0">\n <err code="15">Login failed</err>\n</rsp>\n)
8383

8484
expect(lambda { get }).to raise_error(Pardot::ResponseError)
8585
end
@@ -92,7 +92,7 @@ def get object = "foo", path = "/bar", params = {}
9292

9393
it "should call handle_expired_api_key when the api key expires" do
9494
fake_get "/api/foo/version/4/bar?format=simple",
95-
%(?xml version="1.0" encoding="UTF-8"?>\n<rsp stat="fail" version="1.0">\n <err code="15">Invalid API key or user key</err>\n</rsp>\n)
95+
%(<?xml version="1.0" encoding="UTF-8"?>\n<rsp stat="fail" version="1.0">\n <err code="15">Invalid API key or user key</err>\n</rsp>\n)
9696

9797
expect(@client).to receive(:handle_expired_api_key)
9898
get
@@ -109,7 +109,7 @@ def post object = "foo", path = "/bar", params = {}
109109

110110
it "should notice errors and raise them as Pardot::ResponseError" do
111111
fake_post "/api/foo/version/4/bar?format=simple",
112-
%(?xml version="1.0" encoding="UTF-8"?>\n<rsp stat="fail" version="1.0">\n <err code="15">Login failed</err>\n</rsp>\n)
112+
%(<?xml version="1.0" encoding="UTF-8"?>\n<rsp stat="fail" version="1.0">\n <err code="15">Login failed</err>\n</rsp>\n)
113113

114114
expect(lambda { post }).to raise_error(Pardot::ResponseError)
115115
end
@@ -122,7 +122,7 @@ def post object = "foo", path = "/bar", params = {}
122122

123123
it "should call handle_expired_api_key when the api key expires" do
124124
fake_post "/api/foo/version/4/bar?format=simple",
125-
%(?xml version="1.0" encoding="UTF-8"?>\n<rsp stat="fail" version="1.0">\n <err code="15">Invalid API key or user key</err>\n</rsp>\n)
125+
%(<?xml version="1.0" encoding="UTF-8"?>\n<rsp stat="fail" version="1.0">\n <err code="15">Invalid API key or user key</err>\n</rsp>\n)
126126

127127
expect(@client).to receive(:handle_expired_api_key)
128128
post
Lines changed: 47 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,54 @@
11
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
22

33
describe Pardot::Objects::CustomFields do
4-
5-
before do
6-
@client = create_client
7-
end
8-
9-
describe "query" do
10-
11-
def sample_results
12-
%(<?xml version="1.0" encoding="UTF-8"?>\n<rsp stat="ok" version="1.0">
13-
<result>
14-
<total_results>1</total_results>
15-
<customField>
16-
<created_at>2019-11-26 13:40:37</created_at>
17-
<crm_id null="true" />
18-
<field_id>CustomObject1574793618883</field_id>
19-
<id>8932</id>
20-
<is_record_multiple_responses>false</is_record_multiple_responses>
21-
<is_use_values>false</is_use_values>
22-
<name>Ω≈ç√∫˜µ≤≥÷</name>
23-
<type>Text</type>
24-
<type_id>1</type_id>
25-
<updated_at>2019-11-26 13:40:37</updated_at>
26-
</customField>
27-
</result>
28-
</rsp>)
29-
end
4+
create_auth_managers.each do |auth_manager|
5+
context auth_manager.test_name_suffix do
6+
let(:client) { auth_manager.create_client }
307

31-
before do
32-
@client = create_client
8+
describe "query" do
9+
10+
def sample_results
11+
%(<?xml version="1.0" encoding="UTF-8"?>\n<rsp stat="ok" version="1.0">
12+
<result>
13+
<total_results>1</total_results>
14+
<customField>
15+
<created_at>2019-11-26 13:40:37</created_at>
16+
<crm_id null="true" />
17+
<field_id>CustomObject1574793618883</field_id>
18+
<id>8932</id>
19+
<is_record_multiple_responses>false</is_record_multiple_responses>
20+
<is_use_values>false</is_use_values>
21+
<name>Ω≈ç√∫˜µ≤≥÷</name>
22+
<type>Text</type>
23+
<type_id>1</type_id>
24+
<updated_at>2019-11-26 13:40:37</updated_at>
25+
</customField>
26+
</result>
27+
</rsp>)
28+
end
29+
30+
it "should take in some arguments" do
31+
fake_get "/api/customField/version/3/do/query?id_greater_than=200&format=simple", sample_results
32+
33+
expect(client.custom_fields.query(:id_greater_than => 200)).to eq({"total_results" => 1,
34+
"customField"=>
35+
{
36+
"id"=>"8932",
37+
"name"=>"Ω≈ç√∫˜µ≤≥÷",
38+
"field_id"=>"CustomObject1574793618883",
39+
"type"=>"Text",
40+
"type_id"=>"1",
41+
"crm_id"=>{"null"=>"true"},
42+
"is_record_multiple_responses"=>"false",
43+
"is_use_values"=>"false",
44+
"created_at"=>"2019-11-26 13:40:37",
45+
"updated_at"=>"2019-11-26 13:40:37"
46+
}
47+
})
48+
assert_authorization_header auth_manager
49+
end
50+
51+
end
3352
end
34-
35-
it "should take in some arguments" do
36-
fake_get "/api/customField/version/3/do/query?id_greater_than=200&format=simple", sample_results
37-
38-
expect(@client.custom_fields.query(:id_greater_than => 200)).to eq({"total_results" => 1,
39-
"customField"=>
40-
{
41-
"id"=>"8932",
42-
"name"=>"Ω≈ç√∫˜µ≤≥÷",
43-
"field_id"=>"CustomObject1574793618883",
44-
"type"=>"Text",
45-
"type_id"=>"1",
46-
"crm_id"=>{"null"=>"true"},
47-
"is_record_multiple_responses"=>"false",
48-
"is_use_values"=>"false",
49-
"created_at"=>"2019-11-26 13:40:37",
50-
"updated_at"=>"2019-11-26 13:40:37"
51-
}
52-
})
53-
assert_authorization_header
54-
end
55-
5653
end
57-
5854
end

0 commit comments

Comments
 (0)