Skip to content
This repository has been archived by the owner on Feb 15, 2024. It is now read-only.

Commit

Permalink
Merge pull request #7 from trineo/oauth
Browse files Browse the repository at this point in the history
Oauth
  • Loading branch information
Malcolm Locke committed Oct 27, 2015
2 parents 77bec20 + 6d4eee4 commit efd9125
Show file tree
Hide file tree
Showing 18 changed files with 405 additions and 214 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ Gemfile.lock
pkg/*
doc/*
.rvmrc
use-cases/
.idea*
37 changes: 37 additions & 0 deletions README.rdoc
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,40 @@ This gem provides access to Vend's REST API.

At the time of writing, this is located at
https://docs.google.com/document/pub?id=13JiV6771UcrkmawFRxzvVl3tf5PGSTBH56RYy0-5cps

== Using Vend API via Oauth 2.0

You will need to implement the following steps to connect your application to a Vend Retailer account.

=====1 Requesting authorisation url. User must be redirected to this url in order to receive authorisation code.

auth_code = Vend::Oauth2::AuthCode.new(STORE, CLIENT_ID, SECRET, REDIRECT_URI)
url = auth_code.authorize_url

Url will look like:
https://secure.vendhq.com/connect?response_type=code&client_id={client_id}&redirect_uri={redirect_uri}&state={state}

After successful authorisation, you will be redirected to:
{redirect_uri}?code={code}&domain_prefix={domain_prefix}&state={state}


=====2 Requesting access token.

auth_code = Vend::Oauth2::AuthCode.new(STORE, CLIENT_ID, SECRET, REDIRECT_URI)
token = auth_code.token_from_code(params[:code])

Where <b>token</b> is an instance of OAuth2::AccessToken http://www.rubydoc.info/gems/oauth2/1.0.0/OAuth2/AccessToken

=====3 Using the token to access the Vend API.

client = Vend::Oauth2::Client.new(STORE, AUTH_TOKEN)
client.Product.all.each do |product|
puts product.name
end

=====4 Refreshing the access token

auth_code = Vend::Oauth2::AuthCode.new(STORE, CLIENT_ID, SECRET, REDIRECT_URI)
token = auth_code.refresh_token(auth_token, refresh_token)

The response payload may include a refresh token, if so, you need to update your currently stored refresh token.
3 changes: 3 additions & 0 deletions lib/vend.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@

require 'vend/http_client'
require 'vend/client'

require 'vend/oauth2/client'
require 'vend/oauth2/auth_code'
5 changes: 2 additions & 3 deletions lib/vend/base.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
module Vend

# This Base class provides the basic mapping between Vend::Resource subclasses
# and the HTTP endpoints in the Vend API.
#
Expand Down Expand Up @@ -90,9 +89,9 @@ def self.url_scope(method_name)
end
available_scopes << method_name
end

def self.findable_by(field, options = {})

(class << self ; self ; end).instance_eval do
define_method("find_by_#{field}") do |client, *args|
search(client, options[:as] || field, *args)
Expand Down
6 changes: 4 additions & 2 deletions lib/vend/http_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ class HttpClient

include Logable

attr_accessor :base_url, :verify_ssl, :username, :password
attr_accessor :base_url, :verify_ssl, :username, :password, :auth_token
alias :verify_ssl? :verify_ssl

def initialize(options = {})
@base_url = options[:base_url]
@username = options[:username]
@password = options[:password]
@auth_token = options[:auth_token]
@verify_ssl = if options.has_key?(:verify_ssl)
options[:verify_ssl]
else
Expand Down Expand Up @@ -74,7 +75,8 @@ def request(path, options = {})
# FIXME extract method
method = ("Net::HTTP::" + options[:method].to_s.classify).constantize
request = method.new(url.path + url_params_for(options[:url_params]))
request.basic_auth username, password
request.basic_auth username, password if username && password
request['Authorization'] = "Bearer #{auth_token}" if auth_token

request.body = options[:body] if options[:body]
logger.debug url
Expand Down
48 changes: 48 additions & 0 deletions lib/vend/oauth2/auth_code.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
require 'oauth2'

module Vend
module Oauth2

class AuthCode

DEFAULT_OPTIONS = {}
AUTHORIZE_URL = '/connect'
TOKEN_URL = '/api/1.0/token'

attr_accessor :store, :client_id, :secret, :redirect_uri

def initialize(store, client_id, secret, redirect_uri, options = {})
@store = store
@client_id = client_id
@secret = secret
@redirect_uri = redirect_uri
@options = DEFAULT_OPTIONS.merge(options)
end

def authorize_url
get_oauth2_client.auth_code.authorize_url(:redirect_uri => redirect_uri)
end

def token_from_code(code)
client = get_oauth2_client(store)
client.auth_code.get_token(code, :redirect_uri => redirect_uri)
end

def refresh_token(auth_token, refresh_token)
access_token = OAuth2::AccessToken.new(get_oauth2_client(store), auth_token, {refresh_token: refresh_token})
access_token.refresh!
end

protected
def get_oauth2_client(domain_prefix = 'secure')
OAuth2::Client.new(client_id, secret, {
site: "https://#{domain_prefix}.vendhq.com",
authorize_url: AUTHORIZE_URL,
token_url: TOKEN_URL
})
end

end

end
end
31 changes: 31 additions & 0 deletions lib/vend/oauth2/client.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
module Vend
module Oauth2

class Client < Vend::Client

DEFAULT_OPTIONS = {}

include Logable

attr_accessor :store, :auth_token

def initialize(store, auth_token, options = {})
@store = store
@auth_token = auth_token
@options = DEFAULT_OPTIONS.merge(options)
end

def http_client
@http_client ||= Vend::HttpClient.new(http_client_options)
end

def http_client_options
options.merge(
:auth_token => @auth_token, :base_url => base_url
)
end

end

end
end
2 changes: 1 addition & 1 deletion lib/vend/resource_collection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def increment_page
end

def get_scope(name)

result = scopes.find { |scope| scope.name == name }
if result.nil?
raise ScopeNotFoundError.new(
Expand Down
1 change: 0 additions & 1 deletion spec/integration/product_spec.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
require 'spec_helper'

describe Vend::Resource::Product do

let(:expected_attributes) do
{
'id' => '6cc53042-3d5f-11e0-8697-4040f540b50a',
Expand Down
5 changes: 4 additions & 1 deletion spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@
require 'rubygems'
require 'bundler/setup'
require 'webmock/rspec'
require 'rspec/its'
require 'pry'
Dir["./spec/support/**/*.rb"].each {|f| require f}

require 'cgi'
require 'vend'

RSpec.configure do |config|

config.mock_with(:rspec) { |c| c.syntax = [:should, :expect] }
config.expect_with(:rspec) { |c| c.syntax = [:should, :expect] }
end

def get_mock_response(file)
Expand Down
2 changes: 1 addition & 1 deletion spec/support/matchers/have_attributes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
end
end

failure_message_for_should do |actual|
failure_message do |actual|
"expected #{actual.attrs} to match #{expected}"
end
end
32 changes: 15 additions & 17 deletions spec/support/shared_examples/integration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@ def build_receiver
end

shared_examples "a resource with a collection GET endpoint" do

let(:username) {"foo"}
let(:password) {"bar"}
let(:store) {"baz"}
let(:username) { "foo".freeze }
let(:password) { "bar".freeze }
let(:store) { "baz".freeze }
let(:append_to_url) { '' }

let(:client) do
Expand All @@ -33,23 +32,23 @@ def build_receiver
username, password, store, class_basename.to_s.underscore.pluralize,
append_to_url
]

stub_request(:get, url).to_return(
:status => 200, :body => get_mock_from_path(:get)
)

collection = build_receiver.all
collection.count.should == expected_collection_length
expect(collection.count).to eq(expected_collection_length)

first = collection.first
first.should have_attributes(expected_attributes)
expect(first).to have_attributes(expected_attributes)
end
end

shared_examples "a resource with a singular GET endpoint" do

let(:username) {"foo"}
let(:password) {"bar"}
let(:store) {"baz"}
let(:username) { "foo".freeze }
let(:password) { "bar".freeze }
let(:store) { "baz".freeze }

let(:client) do
Vend::Client.new(store, username, password)
Expand All @@ -65,21 +64,20 @@ def build_receiver
end

shared_examples "a resource with a DELETE endpoint" do

let(:username) {"foo"}
let(:password) {"bar"}
let(:store) {"baz"}
let(:username) { "foo".freeze }
let(:password) { "bar".freeze }
let(:store) { "baz".freeze }

let(:client) do
Vend::Client.new(store, username, password)
end

it "deletes the resource" do

stub_request(:delete, "https://#{username}:#{password}@#{store}.vendhq.com/api/#{class_basename.to_s.underscore.pluralize}/#{expected_attributes['id']}").
to_return(:status => 200, :body => {})
to_return(status: 200, body: {}.to_json)

objekt = build_receiver.build(expected_attributes)
objekt.delete.should be_true
expect(objekt.delete).to be_truthy
end

end
Loading

0 comments on commit efd9125

Please sign in to comment.