Google and OpenID Connect auth backends

closes CNVS-19536, CNVS-19540, CNVS-19545

test plan:
 * test google auth
 * re-test the other oauth providers, cause more stuff got refactored

Change-Id: Ib1c0332cc31f0825f171f3281bf7255abb602844
Reviewed-on: https://gerrit.instructure.com/54526
Tested-by: Jenkins
QA-Review: August Thornton <august@instructure.com>
Reviewed-by: Ethan Vizitei <evizitei@instructure.com>
Product-Review: Cody Cutrer <cody@instructure.com>
This commit is contained in:
Cody Cutrer 2015-05-18 14:06:28 -06:00
parent ddcfaa948e
commit 9ec7c4ee0c
27 changed files with 426 additions and 77 deletions

View File

@ -179,14 +179,15 @@ class AccountAuthorizationConfigsController < ApplicationController
# @API Create Authorization Config
#
# Add external account authentication service(s) for the account.
# Services may be CAS, Facebook, GitHub, LDAP, LinkedIn, SAML, or Twitter.
# Services may be CAS, Facebook, GitHub, Google, LDAP, OpenID Connect,
# LinkedIn, SAML, or Twitter.
#
# Each authentication service is specified as a set of parameters as
# described below. A service specification must include an 'auth_type'
# parameter with a value of 'cas', 'facebook', 'github', 'ldap', 'linkedin',
# 'saml', or 'twitter'. The other recognized parameters depend on this
# auth_type; unrecognized parameters are discarded. Service specifications
# not specifying a valid auth_type are ignored.
# parameter with a value of 'cas', 'facebook', 'github', 'google', 'ldap',
# 'linkedin', 'openid_connect', 'saml', or 'twitter'. The other recognized
# parameters depend on this auth_type; unrecognized parameters are discarded.
# Service specifications not specifying a valid auth_type are ignored.
#
# _Deprecated_[2015-05-08] Any service specification may include an
# optional 'login_handle_name' parameter. This parameter specifies the
@ -244,6 +245,18 @@ class AccountAuthorizationConfigsController < ApplicationController
# The GitHub application's Client Secret. Not available if configured
# globally for Canvas.
#
# For Google, the additional recognized parameters are:
#
# - client_id [Required]
#
# The Google application's Client ID. Not available if configured globally
# for Canvas.
#
# - client_secret [Required]
#
# The Google application's Client Secret. Not available if configured
# globally for Canvas.
#
# For LDAP authentication services, the additional recognized parameters are:
#
# - auth_host
@ -299,6 +312,25 @@ class AccountAuthorizationConfigsController < ApplicationController
# The LinkedIn application's Client Secret. Not available if configured
# globally for Canvas.
#
# For OpenID Connect, the additional recognized parameters are:
#
# - client_id [Required]
#
# The application's Client ID.
#
# - client_secret [Required]
#
# The application's Client Secret.
#
# - authorize_url [Required]
#
# The URL for getting starting the OAuth 2.0 web flow
#
# - token_url [Required]
#
# The URL for exchanging the OAuth 2.0 authorization code for an access
# token and id token
#
# For SAML authentication services, the additional recognized parameters are:
#
# - idp_entity_id

View File

@ -1887,7 +1887,12 @@ class ApplicationController < ActionController::Base
end
def google_drive_client(refresh_token=nil, access_token=nil)
client_secrets = Canvas::Plugin.find(:google_drive).try(:settings)
settings = Canvas::Plugin.find(:google_drive).try(:settings) || {}
client_secrets = {
client_id: settings[:client_id],
client_secret: settings[:client_secret_dec],
redirect_uri: settings[:redirect_uri]
}.with_indifferent_access
GoogleDrive::Client.create(client_secrets, refresh_token, access_token)
end

View File

@ -0,0 +1,21 @@
#
# Copyright (C) 2015 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/>.
#
module Login
GoogleController = Oauth2Controller
end

View File

@ -19,19 +19,24 @@
class Login::Oauth2Controller < Login::OauthBaseController
def new
super
state = session[:oauth2_state] = SecureRandom.hex(24)
redirect_to @aac.authorize_url(redirect_uri, state)
nonce = session[:oauth2_nonce] = SecureRandom.hex(24)
jwt = Canvas::Security.create_jwt(aac_id: @aac.global_id, nonce: nonce)
redirect_to @aac.generate_authorize_url(oauth2_login_callback_url, jwt)
end
def create
super
raise ActiveRecord::RecordNotFound unless @aac.is_a?(AccountAuthorizationConfig::Oauth2)
reset_session_for_login
check_csrf
if jwt['nonce'] != session.delete(:oauth2_nonce)
raise ActionController::InvalidAuthenticityToken
end
@aac = AccountAuthorizationConfig.find(jwt['aac_id'])
raise ActiveRecord::RecordNotFound unless @aac.is_a?(AccountAuthorizationConfig::Oauth2)
unique_id = nil
return unless timeout_protection do
token = @aac.get_token(params[:code], redirect_uri)
token = @aac.get_token(params[:code], oauth2_login_callback_url)
unique_id = @aac.unique_id(token)
end
@ -40,13 +45,11 @@ class Login::Oauth2Controller < Login::OauthBaseController
protected
def check_csrf
if params[:state].blank? || params[:state] != session.delete(:oauth2_state)
raise ActionController::InvalidAuthenticityToken
end
end
def redirect_uri
oauth2_login_callback_url(id: @aac)
def jwt
@jwt ||= if params[:state].present?
Canvas::Security.decode_jwt(params[:state])
else
{}
end
end
end

View File

@ -37,12 +37,6 @@ class Login::OauthBaseController < ApplicationController
reset_session_for_login
end
def create
reset_session_for_login
@aac = @domain_root_account.account_authorization_configs.find(params[:id])
end
protected
def timeout_protection

View File

@ -34,7 +34,9 @@ class Login::OauthController < Login::OauthBaseController
end
def create
super
reset_session_for_login
@aac = @domain_root_account.account_authorization_configs.find(params[:id])
raise ActiveRecord::RecordNotFound unless @aac.is_a?(AccountAuthorizationConfig::Oauth)
oauth_state = session.delete(:oauth)

View File

@ -0,0 +1,21 @@
#
# Copyright (C) 2015 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/>.
#
module Login
OpenidConnectController = Oauth2Controller
end

View File

@ -7,7 +7,7 @@ module Login::Shared
:enrollment,
:expected_user_id,
:masquerade_return_to,
:oauth2_state)
:oauth2_nonce)
end
def successful_login(user, pseudonym, otp_passed = false)

View File

@ -41,17 +41,23 @@ class AccountAuthorizationConfig < ActiveRecord::Base
case type_name
when 'cas', 'ldap', 'saml'
const_get(type_name.upcase)
when 'facebook', 'twitter'
when 'facebook', 'google', 'twitter'
const_get(type_name.classify)
when 'github'
GitHub
when 'linkedin'
LinkedIn
when 'openid_connect'
OpenIDConnect
else
super
end
end
def self.sti_name
display_name.underscore
end
def self.singleton?
false
end
@ -60,6 +66,10 @@ class AccountAuthorizationConfig < ActiveRecord::Base
true
end
def self.display_name
name.demodulize
end
belongs_to :account
acts_as_list scope: :account
@ -73,7 +83,7 @@ class AccountAuthorizationConfig < ActiveRecord::Base
:client_id, :client_secret, :domain,
:consumer_key, :consumer_secret
VALID_AUTH_TYPES = %w[cas facebook github ldap linkedin saml twitter].freeze
VALID_AUTH_TYPES = %w[cas facebook github google ldap linkedin openid_connect saml twitter].freeze
validates_inclusion_of :auth_type, in: VALID_AUTH_TYPES, message: "invalid auth_type, must be one of #{VALID_AUTH_TYPES.join(',')}"
validates_presence_of :account_id
@ -119,5 +129,6 @@ end
# so it doesn't get mixed up with ::CAS, ::LinkedIn and ::Twitter
require_dependency 'account_authorization_config/cas'
require_dependency 'account_authorization_config/google'
require_dependency 'account_authorization_config/linked_in'
require_dependency 'account_authorization_config/twitter'

View File

@ -21,10 +21,6 @@ class AccountAuthorizationConfig::Facebook < AccountAuthorizationConfig::Oauth2
true
end
def self.sti_name
'facebook'
end
def self.recognized_params
if globally_configured?
[ :auth_type ].freeze

View File

@ -0,0 +1,61 @@
#
# Copyright (C) 2015 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/>.
#
class AccountAuthorizationConfig::Google < AccountAuthorizationConfig::OpenIDConnect
def self.singleton?
true
end
def self.recognized_params
if globally_configured?
[ :auth_type ]
else
[ :auth_type, :client_id, :client_secret ].freeze
end
end
def self.globally_configured?
Canvas::Plugin.find(:google_drive).enabled?
end
def client_id
self.class.globally_configured? ? settings[:client_id] : super
end
def client_secret
if self.class.globally_configured?
settings[:client_secret_dec]
else
super
end
end
protected
def settings
Canvas::Plugin.find(:google_drive).settings
end
def client_options
{
site: 'https://accounts.google.com'.freeze,
authorize_url: '/o/oauth2/auth'.freeze,
token_url: '/o/oauth2/token'.freeze
}
end
end

View File

@ -45,7 +45,7 @@ class AccountAuthorizationConfig::LinkedIn < AccountAuthorizationConfig::Oauth2
if self.class.globally_configured?
settings[:client_secret_dec]
else
auth_decrypted_password
super
end
end

View File

@ -23,22 +23,28 @@ class AccountAuthorizationConfig::Oauth2 < AccountAuthorizationConfig::Delegated
SENSITIVE_PARAMS = [ :client_secret ].freeze
# rename DB fields to something that makes sense for OAuth2
def client_id=(val)
self.entity_id = val
end
def client_id
entity_id
end
alias_method :client_secret=, :auth_password=
alias_method :client_secret, :auth_decrypted_password
{ client_id: :entity_id,
site: :auth_host,
authorize_url: :log_in_url,
token_url: :auth_base }.each do |(new_name, old_name)|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{new_name}=(val)
self.#{old_name} = val
end
def #{new_name}
#{old_name}
end
RUBY
end
def client
@client ||= OAuth2::Client.new(client_id, client_secret, client_options)
end
def authorize_url(redirect_uri, state)
def generate_authorize_url(redirect_uri, state)
client.auth_code.authorize_url({ redirect_uri: redirect_uri, state: state }.merge(authorize_options))
end
@ -48,6 +54,14 @@ class AccountAuthorizationConfig::Oauth2 < AccountAuthorizationConfig::Delegated
protected
def client_options
{
site: site,
authorize_url: authorize_url,
token_url: token_url
}
end
def authorize_options
{}
end

View File

@ -0,0 +1,41 @@
#
# Copyright (C) 2015 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/>.
#
class AccountAuthorizationConfig::OpenIDConnect < AccountAuthorizationConfig::Oauth2
def self.sti_name
self == OpenIDConnect ? 'openid_connect'.freeze : super
end
def self.display_name
self == OpenIDConnect ? 'OpenID Connect'.freeze : super
end
def self.recognized_params
[ :auth_type, :client_id, :client_secret, :authorize_url, :token_url ].freeze
end
def unique_id(token)
JWT.decode(token.params['id_token'], nil, false).first['sub']
end
protected
def authorize_options
{ scope: 'openid' }
end
end

View File

@ -21,10 +21,6 @@ class AccountAuthorizationConfig::Twitter < AccountAuthorizationConfig::Oauth
true
end
def self.sti_name
'twitter'
end
def self.recognized_params
if globally_configured?
[ :auth_type ].freeze

View File

@ -72,7 +72,7 @@ class AccountAuthorizationConfigsPresenter
def sso_options
new_auth_types.map do |auth_type|
[auth_type.name.sub(/^AccountAuthorizationConfig::/, ''), auth_type.sti_name]
[auth_type.display_name, auth_type.sti_name]
end
end

View File

@ -0,0 +1,45 @@
<%= fields_for(:account_authorization_config, aac) do |f| %>
<%= f.hidden_field :auth_type, value: 'google' %>
<%= f.hidden_field :id %>
<table class="formtable" style="margin-left: 20px;">
<%= render partial: "form_header",
locals: { auth_type: t("Google"), presenter: presenter, aac: aac } %>
<% unless AccountAuthorizationConfig::Google.globally_configured? %>
<tr>
<td></td>
<td>
<%= mt(<<-TEXT, google_url: "https://console.developers.google.com/project", callback_url: oauth2_login_callback_url)
You will ned to create a web application in the [Google Developer Console](%{google_url}).
You should add %{callback_url} as a redirect URI.
After you create your app, make a note of the Client ID and Client Secret, and enter them here.
TEXT
%>
</td>
</tr>
<tr>
<td style="vertical-align: top; width: 200px;">
<%= f.blabel :client_id, en: "Client ID" %>
</td>
<td style="vertical-align: top;" class="nobr">
<%= f.text_field :client_id, class: "auth_form", style: "width: 300px;" %>
</td>
</tr>
<tr>
<td style="vertical-align: top; width: 200px;">
<%= f.blabel :client_secret, en: "Client Secret" %>
</td>
<td style=" vertical-align: top;" class="nobr">
<%= f.text_field :client_secret, value: '', class: "auth_form" %>
<% if aac.auth_crypted_password -%>
<span class="auth_form" style="font-size: smaller;">
<br/>
<%= t("Leave blank to continue using the current client secret.") %>
</span>
<% end %>
</td>
</tr>
<% end %>
<%= render partial: "form_footer",
locals: { presenter: presenter, account: account, aac: aac, f: f } %>
</table>
<% end %>

View File

@ -0,0 +1,68 @@
<%= fields_for(:account_authorization_config, aac) do |f| %>
<%= f.hidden_field :auth_type, value: 'openid_connect' %>
<%= f.hidden_field :id %>
<table class="formtable" style="margin-left: 20px;">
<%= render partial: "form_header",
locals: { auth_type: 'OpenID Connect', presenter: presenter, aac: aac } %>
<tr>
<td></td>
<td>
<%= mt(<<-TEXT, callback_url: oauth2_login_callback_url)
To configure an OpenID Connect provider you need the authorize and token urls, and
obtain a Client ID and Client Secret.
If the provider can configure valid callback or redirect URIs, use %{callback_url}.
TEXT
%>
</td>
</tr>
<tr>
<td style="vertical-align: top; width: 200px;">
<%= f.blabel :client_id, en: "Client ID" %>
</td>
<td style="vertical-align: top;" class="nobr">
<%= f.text_field :client_id, class: "auth_form", style: "width: 300px;" %>
</td>
</tr>
<tr>
<td style="vertical-align: top; width: 200px;">
<%= f.blabel :client_secret, en: "Client Secret" %>
</td>
<td style=" vertical-align: top;" class="nobr">
<%= f.text_field :client_secret, value: '', class: "auth_form" %>
<% if aac.auth_crypted_password -%>
<span class="auth_form" style="font-size: smaller;">
<br/>
<%= t("Leave blank to continue using the current client secret.") %>
</span>
<% end %>
</td>
</tr>
<tr>
<td style="vertical-align: top; width: 200px;">
<%= f.blabel :site, en: "Site" %>
</td>
<td style="vertical-align: top;" class="nobr">
<%= f.text_field :site, class: "auth_form", style: "width: 300px;" %>
</td>
</tr>
<tr>
<td style="vertical-align: top; width: 200px;">
<%= f.blabel :authorize_url, en: "Authorize URL" %>
</td>
<td style="vertical-align: top;" class="nobr">
<%= f.text_field :authorize_url, class: "auth_form", style: "width: 300px;" %>
</td>
</tr>
<tr>
<td style="vertical-align: top; width: 200px;">
<%= f.blabel :token_url, en: "Token URL" %>
</td>
<td style="vertical-align: top;" class="nobr">
<%= f.text_field :token_url, class: "auth_form", style: "width: 300px;" %>
</td>
</tr>
<%= render partial: "form_footer",
locals: { presenter: presenter, account: account, aac: aac, f: f } %>
</table>
<% end %>

View File

@ -15,7 +15,7 @@
<tr>
<td><%= f.blabel :client_secret, :en => "Client Secret" %></td>
<td>
<%= f.text_field :client_secret %>
<%= f.password_field :client_secret %>
</td>
</tr>

View File

@ -1,6 +1,11 @@
GoogleDocs::DriveConnection.config = Proc.new do
Canvas::Plugin.find(:google_drive).try(:settings) || ConfigFile.load('google_drive')
settings = Canvas::Plugin.find(:google_drive).try(:settings)
if settings
settings = settings.dup
settings[:client_secret] = settings[:client_secret_dec]
end
settings || ConfigFile.load('google_drive')
end
GoogleDocs::Connection.config = Proc.new do

View File

@ -637,7 +637,10 @@ CanvasRails::Application.routes.draw do
get 'login/facebook' => 'login/facebook#new', as: :facebook_login
get 'login/github' => 'login/github#new', as: :github_login
get 'login/google' => 'login/google#new', as: :google_login
get 'login/linkedin' => 'login/linkedin#new', as: :linkedin_login
get 'login/openid_connect' => 'login/openid_connect#new'
get 'login/openid_connect/:id' => 'login/openid_connect#new', as: :openid_connect_login
get 'login/twitter' => 'login/twitter#new', as: :twitter_login
get 'login/otp' => 'login/otp#new', as: :otp_login

View File

@ -0,0 +1,13 @@
class EncryptGoogleDriveSettings < ActiveRecord::Migration
tag :predeploy
def up
PluginSetting.where(name: 'google_drive').each do |ps|
# do a dance so that we don't delete the unencrypted copy yet
ps.encrypt_settings
ps.initialize_plugin_setting
ps.settings[:client_secret] = ps.settings[:client_secret_dec]
PluginSetting.where(id: ps).update_all(settings: ps.settings.to_yaml)
end
end
end

View File

@ -0,0 +1,8 @@
class DropUnencryptedGoogleDriveSettings < ActiveRecord::Migration
tag :postdeploy
def up
# the encryption callback automatically drops the unencrypted version
PluginSetting.where(name: 'google_drive').each(&:save!)
end
end

View File

@ -94,16 +94,17 @@ Canvas::Plugin.register('google_docs', :collaborations, {
:settings_partial => 'plugins/google_docs_settings',
:validator => 'GoogleDocsValidator'
})
Canvas::Plugin.register('google_drive', nil, {
:name => lambda{ t :name, 'Google Drive' },
:description => lambda{ t :description, 'Google Drive file sharing' },
:website => 'http://drive.google.com',
:author => 'Instructure',
:author_website => 'http://www.instructure.com',
:version => '1.0.0',
:settings_partial => 'plugins/google_drive_settings',
:validator => 'GoogleDriveValidator'
})
Canvas::Plugin.register('google_drive', nil,
name: -> { t :name, 'Google Drive' },
description: -> { t :description, 'Google Drive file sharing' },
website: 'http://drive.google.com',
author: 'Instructure',
author_website: 'http://www.instructure.com',
version: '1.0.0',
settings_partial: 'plugins/google_drive_settings',
validator: 'GoogleDriveValidator',
encrypted_settings: [:client_secret]
)
Canvas::Plugin.register('kaltura', nil, {
:name => lambda{ t :name, 'Kaltura' },
:description => lambda{ t :description, 'Kaltura video/audio recording and playback'},

View File

@ -197,6 +197,14 @@ describe ApplicationController do
end
let(:empty_client_secrets) do
{
client_id: nil,
client_secret: nil,
redirect_uri: nil
}.with_indifferent_access
end
it "uses @real_current_user first" do
mock_real_current_user = mock()
mock_current_user = mock()
@ -204,7 +212,7 @@ describe ApplicationController do
controller.instance_variable_set(:@current_user, mock_current_user)
Rails.cache.expects(:fetch).with(['google_drive_tokens', mock_real_current_user].cache_key).returns(["real_current_user_refresh_token", "real_current_user_access_token"])
GoogleDrive::Client.expects(:create).with({},"real_current_user_refresh_token", "real_current_user_access_token")
GoogleDrive::Client.expects(:create).with(empty_client_secrets,"real_current_user_refresh_token", "real_current_user_access_token")
controller.send(:google_drive_user_client)
end
@ -213,7 +221,7 @@ describe ApplicationController do
controller.instance_variable_set(:@real_current_user, nil)
controller.instance_variable_set(:@current_user, mock_current_user)
Rails.cache.expects(:fetch).with(['google_drive_tokens', mock_current_user].cache_key).returns(["current_user_refresh_token", "current_user_access_token"])
GoogleDrive::Client.expects(:create).with({},"current_user_refresh_token", "current_user_access_token")
GoogleDrive::Client.expects(:create).with(empty_client_secrets,"current_user_refresh_token", "current_user_access_token")
controller.send(:google_drive_user_client)
end
@ -228,7 +236,7 @@ describe ApplicationController do
service_mock.stubs(first: mock(token: "user_refresh_token", access_token: "user_access_token"))
mock_user_services.expects(:where).with(service: "google_drive").returns(service_mock)
GoogleDrive::Client.expects(:create).with({}, "user_refresh_token", "user_access_token")
GoogleDrive::Client.expects(:create).with(empty_client_secrets, "user_refresh_token", "user_access_token")
controller.send(:google_drive_user_client)
end
@ -239,7 +247,7 @@ describe ApplicationController do
session[:oauth_gdrive_access_token] = "access_token"
session[:oauth_gdrive_refresh_token] = "refresh_token"
GoogleDrive::Client.expects(:create).with({}, "refresh_token", "access_token")
GoogleDrive::Client.expects(:create).with(empty_client_secrets, "refresh_token", "access_token")
controller.send(:google_drive_user_client)
end
end

View File

@ -30,40 +30,39 @@ describe Login::Oauth2Controller do
get :new, auth_type: 'facebook'
expect(response).to be_redirect
expect(response.location).to match(%r{^https://www.facebook.com/dialog/oauth\?})
expect(session[:oauth2_state]).to_not be_blank
expect(session[:oauth2_nonce]).to_not be_blank
end
end
describe "#create" do
it "checks the OAuth2 CSRF token" do
session[:oauth2_state] = 'bob'
get :create, id: aac
# it could be a 422, or 0 if error handling isn't enabled properly in specs
expect(response).to_not be_success
expect(response).to_not be_redirect
get :create, id: aac, state: 'garbage'
session[:oauth2_nonce] = 'bob'
jwt = Canvas::Security.create_jwt(aac_id: aac.global_id, nonce: 'different')
get :create, state: jwt
# it could be a 422, or 0 if error handling isn't enabled properly in specs
expect(response).to_not be_success
expect(response).to_not be_redirect
end
it "works" do
session[:oauth2_state] = 'bob'
session[:oauth2_nonce] = 'bob'
aac.any_instantiation.expects(:get_token).returns('token')
aac.any_instantiation.expects(:unique_id).with('token').returns('user')
user_with_pseudonym(username: 'user', active_all: 1)
get :create, id: aac, state: 'bob'
jwt = Canvas::Security.create_jwt(aac_id: aac.global_id, nonce: 'bob')
get :create, state: jwt
expect(response).to redirect_to(dashboard_url(login_success: 1))
end
it "redirects to login if no user found" do
aac.any_instantiation.expects(:get_token).returns('token')
aac.any_instantiation.expects(:unique_id).with('token').returns('user')
controller.expects(:check_csrf)
get :create, id: aac
session[:oauth2_nonce] = 'bob'
jwt = Canvas::Security.create_jwt(aac_id: aac.global_id, nonce: 'bob')
get :create, state: jwt
expect(response).to redirect_to(login_url)
expect(flash[:delegated_message]).to_not be_blank
end

View File

@ -149,8 +149,10 @@ describe AccountAuthorizationConfigsPresenter do
expect(presenter.sso_options).to eq([['CAS', 'cas'],
['Facebook', 'facebook'],
['GitHub', 'github'],
['Google', 'google'],
['LDAP', 'ldap'],
['LinkedIn', 'linkedin'],
['OpenID Connect', 'openid_connect'],
['Twitter', 'twitter']])
end