Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Instagram Auth Changes Merged #625

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/sorcery/controller/submodules/external.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ def self.included(base)

require 'sorcery/providers/base'
require 'sorcery/providers/facebook'
require 'sorcery/providers/instagram'
require 'sorcery/providers/twitter'
require 'sorcery/providers/vk'
require 'sorcery/providers/linkedin'
Expand Down
102 changes: 102 additions & 0 deletions lib/sorcery/providers/instagram.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
module Sorcery
module Providers
# This class adds support for OAuth with Instagram.com.

class Instagram < Base

include Protocols::Oauth2


attr_accessor :access_permissions, :token_url,
:authorization_path, :user_info_path,
:scope, :user_info_fields


def initialize
@site = 'https://api.instagram.com'
@token_url = '/oauth/access_token'
@authorization_path = '/oauth/authorize/'
@user_info_path = '/v1/users/self'
super
end

# provider implements method to build Oauth client
def login_url(params, session)
authorize_url
end


# @override of Base#authorize_url
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't override Base#authorize_url here, but OAuth2 method

def authorize_url(opts={})
@scope = build_access_scope!
super(opts.merge(:token_url => @token_url))
end

# pass oauth2 param `code` provided by instgrm server
def process_callback(params, session)
args = {}.tap do |a|
a[:code] = params[:code] if params[:code]
end
get_access_token(args, token_url: @token_url,
client_id: @key, client_secret: @secret)
end


# see `user_info_mapping` in config/initializer,
# given `user_info_mapping` to specify
# {:db_attribute_name => 'instagram_attr_name'}
# so that Sorcery can build AR model from attr names
#
# NOTE: instead of just getting the user info
# from the access_token (which already returns them),
# testing strategy relies on querying user_info_path
def get_user_hash(access_token)
call_api_params = {
:access_token => access_token.token,
:client_id => access_token[:client_id]
}
response = access_token.get(
"#{user_info_path}?#{call_api_params.to_param}"
)


_user_attrs = Hash.new
_user_attrs[:user_info] = JSON.parse(response.body)['data']
_user_attrs[:uid] = _user_attrs[:user_info]['id']
_user_attrs
end


private

# build access scope attribute for instagram
# e.g. whether to access for `likes` or just basic
def build_access_scope!
valid = /\A(basic|comments|relationships|likes)$/

if !access_permissions.present?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can replace it with if access_permissions.blank? - it's easier to read

_scopes = ["basic"]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"basic" is pushed to that array later anyway, do we need to set it here?

elsif access_permissions.kind_of?(Array)
_scopes = access_permissions
.map(&:to_s)
.grep(valid)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about this validating access here. I mean, what will happen if I provide wrong one, e.g. coments instead of comments - this will not be sent to Instagram, so everything will work, except that I won't have access to comments, but it will be silent, so it will be hard to debug.

elsif access_permissions.kind_of?(String)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any particular reason why we allow string here? In case of Facebook and LinkedIn, we allow only arrays, I suggest the same here

_scopes = access_permissions
.split(/\W+/)
.grep(valid)
end

# basic IS required
# b/c instagram fails on blank values
# and requires `basic` for any additional scope
_scopes.push('basic')

_scopes.uniq.join(' ')

end

end

end

end
89 changes: 58 additions & 31 deletions spec/controllers/controller_oauth2_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@
expect(flash[:notice]).to eq "Success!"
end

[:github, :google, :liveid, :vk].each do |provider|
[:github, :google, :liveid, :vk, :instagram].each do |provider|

describe "with #{provider}" do

Expand Down Expand Up @@ -190,11 +190,14 @@
end

sorcery_reload!([:user_activation,:external], :user_activation_mailer => ::SorceryMailer)
sorcery_controller_property_set(:external_providers, [:facebook, :github, :google, :liveid, :vk])
sorcery_controller_property_set(:external_providers, [:facebook, :github, :google, :liveid, :vk, :instagram])

sorcery_controller_external_property_set(:facebook, :key, "eYVNBjBDi33aa9GkA3w")
sorcery_controller_external_property_set(:facebook, :secret, "XpbeSdCoaKSmQGSeokz5qcUATClRW5u08QWNfv71N8")
sorcery_controller_external_property_set(:facebook, :callback_url, "https://blabla.com")
sorcery_controller_external_property_set(:instagram, :key, "eYVNBjBDi33aa9GkA3w")
sorcery_controller_external_property_set(:instagram, :secret, "XpbeSdCoaKSmQGSeokz5qcUATClRW5u08QWNfv71N8")
sorcery_controller_external_property_set(:instagram, :callback_url, "https://blabla.com")
sorcery_controller_external_property_set(:github, :key, "eYVNBjBDi33aa9GkA3w")
sorcery_controller_external_property_set(:github, :secret, "XpbeSdCoaKSmQGSeokz5qcUATClRW5u08QWNfv71N8")
sorcery_controller_external_property_set(:github, :callback_url, "https://blabla.com")
Expand Down Expand Up @@ -235,7 +238,7 @@
expect(ActionMailer::Base.deliveries.size).to eq old_size
end

[:github, :google, :liveid, :vk].each do |provider|
[:github, :google, :liveid, :vk, :instagram].each do |provider|
it "does not send activation email to external users (#{provider})" do
old_size = ActionMailer::Base.deliveries.size
create_new_external_user provider
Expand Down Expand Up @@ -266,7 +269,7 @@
end
end

%w(facebook github google liveid vk).each do |provider|
%w(facebook github google liveid vk instagram).each do |provider|
context "when #{provider}" do
before(:each) do
sorcery_controller_property_set(:register_login_time, true)
Expand Down Expand Up @@ -306,7 +309,7 @@

let(:user) { double('user', id: 42) }

%w(facebook github google liveid vk).each do |provider|
%w(facebook github google liveid vk instagram).each do |provider|
context "when #{provider}" do
before(:each) do
sorcery_model_property_set(:authentications_class, Authentication)
Expand Down Expand Up @@ -343,31 +346,50 @@
def stub_all_oauth2_requests!
access_token = double(OAuth2::AccessToken)
allow(access_token).to receive(:token_param=)
allow(access_token).to receive(:[]).with(:client_id){
"eYVNBjBDi33aa9GkA3w"
}
response = double(OAuth2::Response)
allow(response).to receive(:body) { {
"id"=>"123",
"name"=>"Noam Ben Ari",
"first_name"=>"Noam",
"last_name"=>"Ben Ari",
"link"=>"https://www.facebook.com/nbenari1",
"hometown"=>{"id"=>"110619208966868", "name"=>"Haifa, Israel"},
"location"=>{"id"=>"106906559341067", "name"=>"Pardes Hanah, Hefa, Israel"},
"bio"=>"I'm a new daddy, and enjoying it!",
"gender"=>"male",
"email"=>"[email protected]",
"timezone"=>2,
"locale"=>"en_US",
"languages"=>[{"id"=>"108405449189952", "name"=>"Hebrew"}, {"id"=>"106059522759137", "name"=>"English"}, {"id"=>"112624162082677", "name"=>"Russian"}],
"verified"=>true,
"updated_time"=>"2011-02-16T20:59:38+0000",
# response for VK auth
"response"=>[
{
"uid"=>"123",
"first_name"=>"Noam",
"last_name"=>"Ben Ari"
}
]}.to_json }
allow(response).to receive(:body) {
{
"id"=>"123",
"name"=>"Noam Ben Ari",
"first_name"=>"Noam",
"last_name"=>"Ben Ari",
"link"=>"https://www.facebook.com/nbenari1",
"hometown"=>{"id"=>"110619208966868", "name"=>"Haifa, Israel"},
"location"=>{"id"=>"106906559341067", "name"=>"Pardes Hanah, Hefa, Israel"},
"bio"=>"I'm a new daddy, and enjoying it!",
"gender"=>"male",
"email"=>"[email protected]",
"timezone"=>2,
"locale"=>"en_US",
"languages"=>[{"id"=>"108405449189952", "name"=>"Hebrew"}, {"id"=>"106059522759137", "name"=>"English"}, {"id"=>"112624162082677", "name"=>"Russian"}],
"verified"=>true,
"updated_time"=>"2011-02-16T20:59:38+0000",
# response for VK auth
"response"=>[
{
"uid"=>"123",
"first_name"=>"Noam",
"last_name"=>"Ben Ari"
}
],
# stubbed response for instagram oauth2;
# thankfully keyed by 'data', so doesn't interfere
# with all the other stubs here
"data" =>{
"username"=>"pnmahoney",
"bio"=>"turn WHAT down?",
"website"=>"",
"profile_picture"=>
"https://photos-d.ak.instagram.com/hphotos-ak-xpa1/10454121_417985815007395_867850883_a.jpg",
"full_name"=>"Patrick Mahoney",
"counts"=>{"media"=>2, "followed_by"=>100, "follows"=>71},
"id"=>"123"
}
}.to_json
}
allow(access_token).to receive(:get) { response }
allow(access_token).to receive(:token) { "187041a618229fdaf16613e96e1caabc1e86e46bbfad228de41520e63fe45873684c365a14417289599f3" }
# access_token params for VK auth
Expand All @@ -376,10 +398,14 @@ def stub_all_oauth2_requests!
end

def set_external_property
sorcery_controller_property_set(:external_providers, [:facebook, :github, :google, :liveid, :vk])
sorcery_controller_property_set(:external_providers, [:facebook, :github, :google, :liveid, :vk, :instagram])
sorcery_controller_external_property_set(:facebook, :key, "eYVNBjBDi33aa9GkA3w")
sorcery_controller_external_property_set(:facebook, :secret, "XpbeSdCoaKSmQGSeokz5qcUATClRW5u08QWNfv71N8")
sorcery_controller_external_property_set(:facebook, :callback_url, "https://blabla.com")
sorcery_controller_external_property_set(:instagram, :key, "eYVNBjBDi33aa9GkA3w")
sorcery_controller_external_property_set(:instagram, :secret, "XpbeSdCoaKSmQGSeokz5qcUATClRW5u08QWNfv71N8")
sorcery_controller_external_property_set(:instagram, :callback_url, "https://blabla.com")
sorcery_controller_external_property_set(:instagram, :scope, ['basic'])
sorcery_controller_external_property_set(:github, :key, "eYVNBjBDi33aa9GkA3w")
sorcery_controller_external_property_set(:github, :secret, "XpbeSdCoaKSmQGSeokz5qcUATClRW5u08QWNfv71N8")
sorcery_controller_external_property_set(:github, :callback_url, "https://blabla.com")
Expand All @@ -399,7 +425,8 @@ def provider_url(provider)
github: "https://github.com/login/oauth/authorize?client_id=#{::Sorcery::Controller::Config.github.key}&display=&redirect_uri=http%3A%2F%2Fblabla.com&response_type=code&scope=&state=",
google: "https://accounts.google.com/o/oauth2/auth?client_id=#{::Sorcery::Controller::Config.google.key}&display=&redirect_uri=http%3A%2F%2Fblabla.com&response_type=code&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile&state=",
liveid: "https://oauth.live.com/authorize?client_id=#{::Sorcery::Controller::Config.liveid.key}&display=&redirect_uri=http%3A%2F%2Fblabla.com&response_type=code&scope=wl.basic+wl.emails+wl.offline_access&state=",
vk: "https://oauth.vk.com/authorize?client_id=#{::Sorcery::Controller::Config.vk.key}&display=&redirect_uri=http%3A%2F%2Fblabla.com&response_type=code&scope=#{::Sorcery::Controller::Config.vk.scope}&state="
vk: "https://oauth.vk.com/authorize?client_id=#{::Sorcery::Controller::Config.vk.key}&display=&redirect_uri=http%3A%2F%2Fblabla.com&response_type=code&scope=#{::Sorcery::Controller::Config.vk.scope}&state=",
instagram: "https://api.instagram.com/oauth/authorize?client_id=#{::Sorcery::Controller::Config.instagram.key}&display=&redirect_uri=http%3A%2F%2Fblabla.com&response_type=code&scope=#{::Sorcery::Controller::Config.instagram.scope}&state="
}[provider]
end

Expand Down
20 changes: 20 additions & 0 deletions spec/rails_app/app/controllers/sorcery_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ def test_http_basic_auth
render text: 'HTTP Basic Auth'
end

def login_at_test_instagram
login_at(:instagram)
end

def login_at_test_twitter
login_at(:twitter)
end
Expand Down Expand Up @@ -110,6 +114,14 @@ def login_at_test_with_state
login_at(:facebook, {state: 'bla'})
end

def test_login_from_instagram
if @user = login_from(:instagram)
redirect_to 'bla', notice: 'Success!'
else
redirect_to 'blu', alert: 'Failed!'
end
end

def test_login_from_twitter
if @user = login_from(:twitter)
redirect_to 'bla', notice: 'Success!'
Expand Down Expand Up @@ -168,6 +180,14 @@ def test_login_from_jira
end
end

def test_return_to_with_external_instagram
if @user = login_from(:instagram)
redirect_back_or_to 'bla', notice: 'Success!'
else
redirect_to 'blu', alert: 'Failed!'
end
end

def test_return_to_with_external_twitter
if @user = login_from(:twitter)
redirect_back_or_to 'bla', notice: 'Success!'
Expand Down
3 changes: 3 additions & 0 deletions spec/rails_app/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
get :test_add_second_provider
get :test_return_to_with_external
get :test_login_from
get :test_login_from_instagram
get :test_login_from_twitter
get :test_login_from_facebook
get :test_login_from_github
Expand All @@ -24,6 +25,7 @@
get :test_login_from_vk
get :test_login_from_jira
get :login_at_test
get :login_at_test_instagram
get :login_at_test_twitter
get :login_at_test_facebook
get :login_at_test_github
Expand All @@ -32,6 +34,7 @@
get :login_at_test_vk
get :login_at_test_jira
get :test_return_to_with_external
get :test_return_to_with_external_instagram
get :test_return_to_with_external_twitter
get :test_return_to_with_external_facebook
get :test_return_to_with_external_github
Expand Down
2 changes: 1 addition & 1 deletion spec/shared_examples/user_shared_examples.rb
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,7 @@ def self.matches?(crypted,*tokens)
User.sorcery_adapter.delete_all
end

[:facebook, :github, :google, :liveid].each do |provider|
[:facebook, :github, :google, :liveid, :instagram].each do |provider|

it "does not send activation email to external users" do
old_size = ActionMailer::Base.deliveries.size
Expand Down