validate client id on refresh token "renewal"
fixes CNVS-28858 also get rid of deprecated messages, and return a proper 401 for invalid client credentials, as per spec test plan: * have a developer key, go through the oauth flow, and get a refresh token * user the refresh token to get a new access token making sure it works * create a second developer key; try to get a new access token with your existing refresh token, but authenticating with the new client credentials * it should fail Change-Id: Ide95e317dd8768abd9fb3ab4eb67225a6e58bbcb Reviewed-on: https://gerrit.instructure.com/77634 Tested-by: Jenkins Reviewed-by: Simon Williams <simon@instructure.com> QA-Review: August Thornton <august@instructure.com> Product-Review: Cody Cutrer <cody@instructure.com>
This commit is contained in:
parent
ac2578c360
commit
0db7878007
|
@ -82,12 +82,12 @@ class Oauth2ProviderController < ApplicationController
|
|||
raise Canvas::Oauth::RequestError, :invalid_client_id unless provider.has_valid_key?
|
||||
raise Canvas::Oauth::RequestError, :invalid_client_secret unless provider.is_authorized_by?(secret)
|
||||
|
||||
|
||||
if grant_type == "authorization_code"
|
||||
raise OAuth2RequestError :authorization_code_not_supplied unless params[:code]
|
||||
|
||||
token = provider.token_for(params[:code])
|
||||
raise Canvas::Oauth::RequestError, :invalid_authorization_code unless token.is_for_valid_code?
|
||||
raise Canvas::Oauth::RequestError, :incorrect_client unless token.key.id == token.client_id
|
||||
|
||||
token.create_access_token_if_needed(value_to_boolean(params[:replace_tokens]))
|
||||
Canvas::Oauth::Token.expire_code(params[:code])
|
||||
|
@ -97,6 +97,7 @@ class Oauth2ProviderController < ApplicationController
|
|||
token = provider.token_for_refresh_token(params[:refresh_token])
|
||||
# token = AccessToken.authenticate_refresh_token(params[:refresh_token])
|
||||
raise Canvas::Oauth::RequestError, :invalid_refresh_token unless token
|
||||
raise Canvas::Oauth::RequestError, :incorrect_client unless token.access_token.developer_key_id == token.key.id
|
||||
token.access_token.regenerate_access_token
|
||||
else
|
||||
raise Canvas::Oauth::RequestError, :unsupported_grant_type
|
||||
|
@ -114,6 +115,7 @@ class Oauth2ProviderController < ApplicationController
|
|||
|
||||
private
|
||||
def oauth_error(exception)
|
||||
response['WWW-Authenticate'] = 'Canvas OAuth 2.0' if exception.http_status == 401
|
||||
return render(exception.to_render_data)
|
||||
end
|
||||
|
||||
|
|
|
@ -4,89 +4,81 @@ module Canvas::Oauth
|
|||
invalid_client_id: {
|
||||
error: :invalid_client,
|
||||
error_description: "unknown client",
|
||||
http_status: 400,
|
||||
legacy: "invalid client_id"
|
||||
},
|
||||
http_status: 401
|
||||
}.freeze,
|
||||
|
||||
invalid_client_secret: {
|
||||
error: :invalid_client,
|
||||
error_description: "invalid client",
|
||||
http_status: 400,
|
||||
legacy: "invalid client_secret"
|
||||
},
|
||||
http_status: 401
|
||||
}.freeze,
|
||||
|
||||
invalid_redirect: {
|
||||
error: :invalid_request,
|
||||
error_description: "redirect_uri does not match client settings",
|
||||
http_status: 400,
|
||||
legacy: "invalid redirect_uri"
|
||||
},
|
||||
error_description: "redirect_uri does not match client settings"
|
||||
}.freeze,
|
||||
|
||||
invalid_refresh_token: {
|
||||
error: :invalid_request,
|
||||
error_description: "refresh_token not found",
|
||||
http_status: 400,
|
||||
},
|
||||
error_description: "refresh_token not found"
|
||||
}.freeze,
|
||||
|
||||
invalid_authorization_code: {
|
||||
error: :invalid_request,
|
||||
error_description: "client does not have access to specified account",
|
||||
http_status: 400,
|
||||
legacy: "invalid code"
|
||||
},
|
||||
error: :invalid_grant,
|
||||
error_description: "authorization_code not found"
|
||||
}.freeze,
|
||||
|
||||
incorrect_client: {
|
||||
error: :invalid_grant,
|
||||
error_description: "incorrect client"
|
||||
}.freeze,
|
||||
|
||||
authorization_code_not_supplied: {
|
||||
error: :invalid_request,
|
||||
error_description: "You must provide the code parameter when using the authorization_code grant type",
|
||||
http_status: 400
|
||||
},
|
||||
error_description: "You must provide the code parameter when using the authorization_code grant type"
|
||||
}.freeze,
|
||||
|
||||
refresh_token_not_supplied: {
|
||||
error: :invalid_request,
|
||||
error_description: "You must provide the refresh_token parameter when using the refresh_token grant type",
|
||||
http_status: 400
|
||||
},
|
||||
error_description: "You must provide the refresh_token parameter when using the refresh_token grant type"
|
||||
}.freeze,
|
||||
|
||||
unsupported_grant_type: {
|
||||
error: :unsupported_grant_type,
|
||||
error_description: "The grant_type you requested is not currently supported",
|
||||
http_status: 400
|
||||
},
|
||||
error_description: "The grant_type you requested is not currently supported"
|
||||
}.freeze,
|
||||
|
||||
client_not_authorized_for_account: {
|
||||
error: :invalid_scope,
|
||||
error_description: "client does not have access to specified account",
|
||||
http_status: 401
|
||||
}
|
||||
}
|
||||
}.freeze
|
||||
}.freeze
|
||||
|
||||
def initialize(message)
|
||||
@message = message
|
||||
end
|
||||
|
||||
def to_json
|
||||
# since we send back there error in the message key instead of the standard error
|
||||
# at some point we should stop sending the message key
|
||||
json_response = {
|
||||
def as_json
|
||||
{
|
||||
error: error_map[:error],
|
||||
error_description: error_map[:error_description]
|
||||
}
|
||||
json_response[:message] = error_map[:legacy] if error_map[:legacy]
|
||||
json_response
|
||||
end
|
||||
|
||||
def to_render_data
|
||||
{
|
||||
status: http_status,
|
||||
json: to_json
|
||||
json: as_json
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
def http_status
|
||||
error_map[:http_status] || 400
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def error_map
|
||||
ERROR_MAP[@message]
|
||||
end
|
||||
|
|
|
@ -20,7 +20,7 @@ module Canvas::Oauth
|
|||
end
|
||||
|
||||
def is_for_valid_code?
|
||||
code_data.present? && client_id == key.id
|
||||
code_data.present?
|
||||
end
|
||||
|
||||
def client_id
|
||||
|
|
|
@ -353,14 +353,14 @@ describe "API Authentication", type: :request do
|
|||
it "should require the developer key to have a redirect_uri" do
|
||||
get "/login/oauth2/auth", :response_type => 'code', :client_id => @client_id, :redirect_uri => "http://www.example.com/oauth2response"
|
||||
expect(response).to be_client_error
|
||||
expect(response.body).to match /invalid redirect_uri/
|
||||
expect(response.body).to match /redirect_uri/
|
||||
end
|
||||
|
||||
it "should require the redirect_uri domains to match" do
|
||||
@key.update_attribute :redirect_uri, 'http://www.example2.com/oauth2response'
|
||||
get "/login/oauth2/auth", :response_type => 'code', :client_id => @client_id, :redirect_uri => "http://www.example.com/oauth2response"
|
||||
expect(response).to be_client_error
|
||||
expect(response.body).to match /invalid redirect_uri/
|
||||
expect(response.body).to match /redirect_uri/
|
||||
|
||||
@key.update_attribute :redirect_uri, 'http://www.example.com/oauth2response'
|
||||
get "/login/oauth2/auth", :response_type => 'code', :client_id => @client_id, :redirect_uri => "http://www.example.com/oauth2response"
|
||||
|
|
|
@ -22,16 +22,17 @@ 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
|
||||
it 'renders a 401 when there is no client_id' do
|
||||
get :auth
|
||||
assert_status(400)
|
||||
expect(response.body).to match /invalid client_id/
|
||||
assert_status(401)
|
||||
expect(response.body).to match /unknown client/
|
||||
expect(response['WWW-Authenticate']).to_not be_blank
|
||||
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/
|
||||
expect(response.body).to match /redirect_uri does not match/
|
||||
end
|
||||
|
||||
it 'redirects to the login url' do
|
||||
|
@ -116,23 +117,23 @@ describe Oauth2ProviderController do
|
|||
redis
|
||||
end
|
||||
|
||||
it 'renders a 400 if theres no client_id' do
|
||||
it 'renders a 401 if theres no client_id' do
|
||||
post :token
|
||||
assert_status(400)
|
||||
expect(response.body).to match /invalid client_id/
|
||||
assert_status(401)
|
||||
expect(response.body).to match /unknown client/
|
||||
end
|
||||
|
||||
it 'renders a 400 if the secret is invalid' do
|
||||
it 'renders a 401 if the secret is invalid' do
|
||||
post :token, :client_id => key.id, :client_secret => key.api_key + "123"
|
||||
assert_status(400)
|
||||
expect(response.body).to match /invalid client_secret/
|
||||
assert_status(401)
|
||||
expect(response.body).to match /invalid client/
|
||||
end
|
||||
|
||||
it 'renders a 400 if the provided code does not match a token' do
|
||||
Canvas.stubs(:redis => redis)
|
||||
post :token, :client_id => key.id, :client_secret => key.api_key, :code => "NotALegitCode"
|
||||
assert_status(400)
|
||||
expect(response.body).to match /invalid code/
|
||||
expect(response.body).to match /authorization_code not found/
|
||||
end
|
||||
|
||||
it 'outputs the token json if everything checks out' do
|
||||
|
@ -143,6 +144,14 @@ describe Oauth2ProviderController do
|
|||
expect(JSON.parse(response.body).keys.sort).to match_array(['access_token', 'refresh_token', 'user', 'expires_in'])
|
||||
end
|
||||
|
||||
it 'renders a 400 if the provided code is for the wrong key' do
|
||||
Canvas.stubs(:redis => redis)
|
||||
key2 = DeveloperKey.create!
|
||||
post :token, client_id: key2.id.to_s, client_secret: key2.api_key, grant_type: 'authorization_code', code: valid_code
|
||||
assert_status(400)
|
||||
expect(response.body).to match(/incorrect client/)
|
||||
end
|
||||
|
||||
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)
|
||||
|
@ -197,17 +206,29 @@ describe Oauth2ProviderController do
|
|||
expect(json['access_token']).to_not eq access_token
|
||||
end
|
||||
|
||||
it 'errors with a mismatched client id' do
|
||||
old_token = user.access_tokens.create! :developer_key => key
|
||||
refresh_token = old_token.plaintext_refresh_token
|
||||
key2 = DeveloperKey.create!
|
||||
|
||||
post :token, client_id: key2.id, client_secret: key2.api_key, grant_type: "refresh_token", refresh_token: refresh_token
|
||||
assert_status(400)
|
||||
expect(response.body).to match(/incorrect client/)
|
||||
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
|
||||
expect(response).to be_success
|
||||
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
|
||||
expect(response).to be_success
|
||||
json = JSON.parse(response.body)
|
||||
expect(json['access_token']).to_not eq access_token
|
||||
end
|
||||
|
|
|
@ -36,11 +36,6 @@ module Canvas::Oauth
|
|||
expect(token.is_for_valid_code?).to be_falsey
|
||||
end
|
||||
|
||||
it 'is false when the client id does not match the key id' do
|
||||
stub_out_cache (key.id + 1)
|
||||
expect(token.is_for_valid_code?).to be_falsey
|
||||
end
|
||||
|
||||
it 'is true otherwise' do
|
||||
expect(token.is_for_valid_code?).to be_truthy
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue