2015-04-02 00:06:16 +08:00
|
|
|
#
|
|
|
|
# Copyright (C) 2011 - 2014 Instructure, Inc.
|
|
|
|
#
|
|
|
|
# This file is part of Canvas.
|
|
|
|
#
|
|
|
|
# Canvas is free software: you can redistribute it and/or modify it under
|
|
|
|
# the terms of the GNU Affero General Public License as published by the Free
|
|
|
|
# Software Foundation, version 3 of the License.
|
|
|
|
#
|
|
|
|
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
|
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
|
|
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
|
|
# details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU Affero General Public License along
|
|
|
|
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
#
|
|
|
|
|
|
|
|
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
|
|
|
|
|
|
|
describe Oauth2ProviderController do
|
|
|
|
describe 'GET auth' do
|
|
|
|
let_once(:key) { DeveloperKey.create! :redirect_uri => 'https://example.com' }
|
|
|
|
|
|
|
|
it 'renders a 400 when there is no client_id' do
|
|
|
|
get :auth
|
|
|
|
assert_status(400)
|
|
|
|
expect(response.body).to match /invalid client_id/
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'renders 400 on a bad redirect_uri' do
|
|
|
|
get :auth, :client_id => key.id
|
|
|
|
assert_status(400)
|
|
|
|
expect(response.body).to match /invalid redirect_uri/
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'redirects to the login url' do
|
|
|
|
get :auth, :client_id => key.id, :redirect_uri => Canvas::Oauth::Provider::OAUTH2_OOB_URI
|
|
|
|
expect(response).to redirect_to(login_url)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'passes on canvas_login if provided' do
|
|
|
|
get :auth, :client_id => key.id, :redirect_uri => Canvas::Oauth::Provider::OAUTH2_OOB_URI, :canvas_login => 1
|
|
|
|
expect(response).to redirect_to(login_url(:canvas_login => 1))
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with a user logged in' do
|
|
|
|
before :once do
|
|
|
|
user_with_pseudonym(:active_all => 1, :password => 'qwerty')
|
|
|
|
end
|
|
|
|
|
|
|
|
before :each do
|
|
|
|
user_session(@user)
|
|
|
|
|
|
|
|
redis = stub('Redis')
|
|
|
|
redis.stubs(:setex)
|
|
|
|
Canvas.stubs(:redis => redis)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'should redirect to the confirm url if the user has no token' do
|
|
|
|
get :auth, :client_id => key.id, :redirect_uri => Canvas::Oauth::Provider::OAUTH2_OOB_URI
|
|
|
|
expect(response).to redirect_to(oauth2_auth_confirm_url)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'redirects to login_url with ?force_login=1' do
|
|
|
|
get :auth, :client_id => key.id, :redirect_uri => Canvas::Oauth::Provider::OAUTH2_OOB_URI, :force_login => 1
|
|
|
|
expect(response).to redirect_to(login_url(:force_login => 1))
|
|
|
|
end
|
|
|
|
|
2015-06-18 03:33:28 +08:00
|
|
|
it 'should redirect to login_url when oauth2 session is nil' do
|
|
|
|
get :confirm
|
|
|
|
expect(flash[:error]).to eq "Must submit new OAuth2 request"
|
|
|
|
expect(response).to redirect_to(login_url)
|
|
|
|
end
|
|
|
|
|
2015-04-02 00:06:16 +08:00
|
|
|
it 'should redirect to the redirect uri if the user already has remember-me token' do
|
|
|
|
@user.access_tokens.create!({:developer_key => key, :remember_access => true, :scopes => ['/auth/userinfo'], :purpose => nil})
|
|
|
|
get :auth, :client_id => key.id, :redirect_uri => 'https://example.com', :scopes => '/auth/userinfo'
|
|
|
|
expect(response).to be_redirect
|
|
|
|
expect(response.location).to match(/https:\/\/example.com/)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'should not reuse userinfo tokens for other scopes' do
|
|
|
|
@user.access_tokens.create!({:developer_key => key, :remember_access => true, :scopes => ['/auth/userinfo'], :purpose => nil})
|
|
|
|
get :auth, :client_id => key.id, :redirect_uri => 'https://example.com'
|
|
|
|
expect(response).to redirect_to(oauth2_auth_confirm_url)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'should redirect to the redirect uri if the developer key is trusted' do
|
|
|
|
key.trusted = true
|
|
|
|
key.save!
|
|
|
|
get :auth, :client_id => key.id, :redirect_uri => 'https://example.com', :scopes => '/auth/userinfo'
|
|
|
|
expect(response).to be_redirect
|
|
|
|
expect(response.location).to match(/https:\/\/example.com/)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-10-02 03:52:57 +08:00
|
|
|
describe 'POST token' do
|
2015-04-02 00:06:16 +08:00
|
|
|
let_once(:key) { DeveloperKey.create! }
|
|
|
|
let_once(:user) { User.create! }
|
|
|
|
let(:valid_code) {"thecode"}
|
|
|
|
let(:valid_code_redis_key) {"#{Canvas::Oauth::Token::REDIS_PREFIX}#{valid_code}"}
|
|
|
|
let(:redis) do
|
|
|
|
redis = stub('Redis')
|
|
|
|
redis.stubs(:get)
|
|
|
|
redis.stubs(:get).with(valid_code_redis_key).returns(%Q{{"client_id": #{key.id}, "user": #{user.id}}})
|
|
|
|
redis.stubs(:del).with(valid_code_redis_key).returns(%Q{{"client_id": #{key.id}, "user": #{user.id}}})
|
|
|
|
redis
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'renders a 400 if theres no client_id' do
|
2015-10-02 03:52:57 +08:00
|
|
|
post :token
|
2015-04-02 00:06:16 +08:00
|
|
|
assert_status(400)
|
|
|
|
expect(response.body).to match /invalid client_id/
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'renders a 400 if the secret is invalid' do
|
2015-10-02 03:52:57 +08:00
|
|
|
post :token, :client_id => key.id, :client_secret => key.api_key + "123"
|
2015-04-02 00:06:16 +08:00
|
|
|
assert_status(400)
|
|
|
|
expect(response.body).to match /invalid client_secret/
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'renders a 400 if the provided code does not match a token' do
|
|
|
|
Canvas.stubs(:redis => redis)
|
2015-10-02 03:52:57 +08:00
|
|
|
post :token, :client_id => key.id, :client_secret => key.api_key, :code => "NotALegitCode"
|
2015-04-02 00:06:16 +08:00
|
|
|
assert_status(400)
|
|
|
|
expect(response.body).to match /invalid code/
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'outputs the token json if everything checks out' do
|
|
|
|
redis.expects(:del).with(valid_code_redis_key).at_least_once
|
|
|
|
Canvas.stubs(:redis => redis)
|
2015-10-02 03:52:57 +08:00
|
|
|
post :token, client_id: key.id, client_secret: key.api_key, grant_type: 'authorization_code', code: valid_code
|
2015-04-02 00:06:16 +08:00
|
|
|
expect(response).to be_success
|
2015-10-09 05:47:54 +08:00
|
|
|
expect(JSON.parse(response.body).keys.sort).to match_array(['access_token', 'refresh_token', 'user', 'expires_in'])
|
2015-04-02 00:06:16 +08:00
|
|
|
end
|
2015-04-16 03:49:31 +08:00
|
|
|
|
2015-10-02 03:52:57 +08:00
|
|
|
it 'default grant_type to authorization_code if none is supplied and code is present' do
|
|
|
|
redis.expects(:del).with(valid_code_redis_key).at_least_once
|
|
|
|
Canvas.stubs(:redis => redis)
|
|
|
|
post :token, :client_id => key.id, :client_secret => key.api_key, :code => valid_code
|
|
|
|
expect(response).to be_success
|
|
|
|
json = JSON.parse(response.body)
|
2015-10-09 05:47:54 +08:00
|
|
|
expect(json.keys.sort).to match_array ['access_token', 'refresh_token', 'user', 'expires_in']
|
2015-10-02 03:52:57 +08:00
|
|
|
end
|
|
|
|
|
2015-04-16 03:49:31 +08:00
|
|
|
it 'deletes existing tokens for the same key when replace_tokens=1' do
|
|
|
|
old_token = user.access_tokens.create! :developer_key => key
|
|
|
|
Canvas.stubs(:redis => redis)
|
2015-10-02 03:52:57 +08:00
|
|
|
post :token, :client_id => key.id, :client_secret => key.api_key, :code => valid_code, :replace_tokens => '1'
|
2015-04-16 03:49:31 +08:00
|
|
|
expect(response).to be_success
|
|
|
|
expect(AccessToken.exists?(old_token.id)).to be(false)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not delete existing tokens without replace_tokens' do
|
|
|
|
old_token = user.access_tokens.create! :developer_key => key
|
|
|
|
Canvas.stubs(:redis => redis)
|
2015-10-02 03:52:57 +08:00
|
|
|
post :token, :client_id => key.id, :client_secret => key.api_key, :code => valid_code
|
2015-04-16 03:49:31 +08:00
|
|
|
expect(response).to be_success
|
|
|
|
expect(AccessToken.exists?(old_token.id)).to be(true)
|
|
|
|
end
|
2015-10-02 03:52:57 +08:00
|
|
|
|
|
|
|
context 'grant_type refresh_token' do
|
|
|
|
it 'must specify grant_type' do
|
|
|
|
post :token, client_id: key.id, client_secret: key.api_key, refresh_token: "SAFASDFASDF"
|
|
|
|
assert_status(400)
|
|
|
|
json = JSON.parse(response.body)
|
|
|
|
expect(json['error']).to eq "unsupported_grant_type"
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'should not generate a new access_token with an invalid refresh_token' do
|
|
|
|
old_token = user.access_tokens.create! :developer_key => key
|
|
|
|
refresh_token = old_token.plaintext_refresh_token
|
|
|
|
|
|
|
|
post :token, client_id: key.id, client_secret: key.api_key, grant_type: "refresh_token", refresh_token: refresh_token + "ASDAS"
|
|
|
|
assert_status(400)
|
|
|
|
json = JSON.parse(response.body)
|
|
|
|
expect(json['error']).to eq "invalid_request"
|
|
|
|
expect(json['error_description']).to eq "refresh_token not found"
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'should generate a new access_token' do
|
|
|
|
old_token = user.access_tokens.create! :developer_key => key
|
|
|
|
refresh_token = old_token.plaintext_refresh_token
|
|
|
|
access_token = old_token.full_token
|
|
|
|
|
|
|
|
post :token, client_id: key.id, client_secret: key.api_key, grant_type: "refresh_token", refresh_token: refresh_token
|
|
|
|
json = JSON.parse(response.body)
|
|
|
|
expect(json['access_token']).to_not eq access_token
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'should be able to regenerate access_token multiple times' do
|
|
|
|
old_token = user.access_tokens.create! :developer_key => key
|
|
|
|
refresh_token = old_token.plaintext_refresh_token
|
|
|
|
access_token = old_token.full_token
|
|
|
|
|
|
|
|
post :token, client_id: key.id, client_secret: key.api_key, grant_type: "refresh_token", refresh_token: refresh_token
|
|
|
|
json = JSON.parse(response.body)
|
|
|
|
expect(json['access_token']).to_not eq access_token
|
|
|
|
|
|
|
|
access_token = json['access_token']
|
|
|
|
post :token, client_id: key.id, client_secret: key.api_key, grant_type: "refresh_token", refresh_token: refresh_token
|
|
|
|
json = JSON.parse(response.body)
|
|
|
|
expect(json['access_token']).to_not eq access_token
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'unsupported grant_type' do
|
|
|
|
it 'returns a 400' do
|
|
|
|
post :token, :client_id => key.id, :client_secret => key.api_key, :grant_type => "client_credentials"
|
|
|
|
assert_status(400)
|
|
|
|
json = JSON.parse(response.body)
|
|
|
|
expect(json['error']).to eq "unsupported_grant_type"
|
|
|
|
end
|
|
|
|
end
|
2015-04-02 00:06:16 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
describe 'POST accept' do
|
|
|
|
let_once(:user) { User.create! }
|
|
|
|
let_once(:key) { DeveloperKey.create! }
|
|
|
|
let(:session_hash) { { :oauth2 => { :client_id => key.id, :redirect_uri => Canvas::Oauth::Provider::OAUTH2_OOB_URI } } }
|
|
|
|
let(:oauth_accept) { post :accept, {}, session_hash }
|
|
|
|
|
|
|
|
before { user_session user }
|
|
|
|
|
|
|
|
it 'uses the global id of the user for generating the code' do
|
|
|
|
Canvas::Oauth::Token.expects(:generate_code_for).with(user.global_id, key.id, {:scopes => nil, :remember_access => nil, :purpose => nil}).returns('code')
|
|
|
|
oauth_accept
|
|
|
|
expect(response).to redirect_to(oauth2_auth_url(:code => 'code'))
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'saves the requested scopes with the code' do
|
|
|
|
scopes = 'userinfo'
|
|
|
|
session_hash[:oauth2][:scopes] = scopes
|
|
|
|
Canvas::Oauth::Token.expects(:generate_code_for).with(user.global_id, key.id, {:scopes => scopes, :remember_access => nil, :purpose => nil}).returns('code')
|
|
|
|
oauth_accept
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'remembers the users access preference with the code' do
|
|
|
|
Canvas::Oauth::Token.expects(:generate_code_for).with(user.global_id, key.id, {:scopes => nil, :remember_access => '1', :purpose => nil}).returns('code')
|
|
|
|
post :accept, {:remember_access => '1'}, session_hash
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'removes oauth session info after code generation' do
|
|
|
|
Canvas::Oauth::Token.stubs(:generate_code_for => 'code')
|
|
|
|
oauth_accept
|
|
|
|
expect(controller.session[:oauth2]).to be_nil
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'forwards the oauth state if it was provided' do
|
|
|
|
session_hash[:oauth2][:state] = '1234567890'
|
|
|
|
Canvas::Oauth::Token.stubs(:generate_code_for => 'code')
|
|
|
|
oauth_accept
|
|
|
|
expect(response).to redirect_to(oauth2_auth_url(:code => 'code', :state => '1234567890'))
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
end
|