add redirect config to account authorization

fixes: CNVS-9194

This adds a configuration to CAS and SAML which will redirect to when an
unknown user is authorized from CAS or SAML but we do not have a
pseudonym for them in canvas yet.  Instead of logging them out of CAS or
SAML it will redirect them to a custom url.  By default if no url is
configured it will redirect to the login page or cas login page.

Test Plan:

CAS:
  - Setup a User on the CAS server that can login.
  - Make sure the user is not in Canvas and wont be matched up to any
    other Canvas pseudonyms.
  - Log in to CAS and Canvas.
  - Canvas should redirect to cas_login_url.
  - Configure the Canvas CAS setting unknown user url to redirect to
    a custom url.
  - Log in to CAS and Canvas
  - Canvas should redirect to the custom url specified.
  - The user should not be logged out of CAS.

SAML:
  - Setup a User on the SAML server that can login.
  - Make sure the user is not in Canvas and wont be matched up to any
    other Canvas pseudonyms.
  - Log in to SAML and Canvas.
  - Canvas should redirect to login_url and provide a flash message
    stating that the user could not be found.
  - Configure the Canvas SAML settings Unknown User Url to redirect
    to a custom url.
  - Log in to SAML and Canvas
  - Canvas should redirect to the custom url specified.
  - The user should not be logged out of SAML.

Change-Id: I29a78f8ec60c94caecf63547584d8ae804ffc9de
Reviewed-on: https://gerrit.instructure.com/38472
Reviewed-by: Cody Cutrer <cody@instructure.com>
Tested-by: Jenkins <jenkins@instructure.com>
QA-Review: August Thornton <august@instructure.com>
Product-Review: Matt Fairbourn <mfairbourn@instructure.com>
This commit is contained in:
Nick Cloward 2014-07-29 09:56:08 -06:00
parent bd906e75bc
commit fbc53265e4
10 changed files with 229 additions and 89 deletions

View File

@ -106,6 +106,11 @@
# "description": "Valid for SAML authorization.",
# "example": "nameid",
# "type": "string"
# },
# "unknown_user_url": {
# "description": "Valid for SAML and CAS authorization.",
# "example": "https://canvas.instructure.com/login",
# "type": "string"
# }
# }
# }
@ -178,6 +183,11 @@ class AccountAuthorizationConfigsController < ApplicationController
# An alternate SSO URL for logging into CAS. You probably should not set
# this.
#
# - unkown_user_url [Optional]
#
# A url to redirect to when a user is authorized through CAS but is not
# found in Canvas.
#
# For SAML authentication services, the additional recognized parameters are:
#
# - idp_entity_id
@ -201,6 +211,11 @@ class AccountAuthorizationConfigsController < ApplicationController
#
# Forgot Password URL. Leave blank for default Canvas behavior.
#
# - unkown_user_url [Optional]
#
# A url to redirect to when a user is authorized through SAML but is not
# found in Canvas.
#
# - identifier_format
#
# The SAML service's identifier format. Must be one of:
@ -273,30 +288,30 @@ class AccountAuthorizationConfigsController < ApplicationController
# @example_request
# # Create LDAP config
# curl 'https://<canvas>/api/v1/accounts/<account_id>/account_authorization_configs' \
# -F 'auth_type=ldap' \
# -F 'auth_host=ldap.mydomain.edu' \
# -F 'auth_filter=(sAMAccountName={{login}})' \
# -F 'auth_username=username' \
# -F 'auth_password=bestpasswordever' \
# -F 'position=1' \
# -F 'auth_type=ldap' \
# -F 'auth_host=ldap.mydomain.edu' \
# -F 'auth_filter=(sAMAccountName={{login}})' \
# -F 'auth_username=username' \
# -F 'auth_password=bestpasswordever' \
# -F 'position=1' \
# -H 'Authorization: Bearer <token>'
#
# @example_request
# # Create SAML config
# curl 'https://<canvas>/api/v1/accounts/<account_id>/account_authorization_configs' \
# -F 'auth_type=saml' \
# -F 'idp_entity_id=<idp_entity_id>' \
# -F 'log_in_url=<login_url>' \
# -F 'log_out_url=<logout_url>' \
# -F 'certificate_fingerprint=<fingerprint>' \
# -F 'auth_type=saml' \
# -F 'idp_entity_id=<idp_entity_id>' \
# -F 'log_in_url=<login_url>' \
# -F 'log_out_url=<logout_url>' \
# -F 'certificate_fingerprint=<fingerprint>' \
# -H 'Authorization: Bearer <token>'
#
# @example_request
# # Create CAS config
# curl 'https://<canvas>/api/v1/accounts/<account_id>/account_authorization_configs' \
# -F 'auth_type=cas' \
# -F 'auth_base=cas.mydomain.edu' \
# -F 'log_in_url=<login_url>' \
# -F 'auth_type=cas' \
# -F 'auth_base=cas.mydomain.edu' \
# -F 'log_in_url=<login_url>' \
# -H 'Authorization: Bearer <token>'
#
# _Deprecated_ Examples:
@ -386,8 +401,8 @@ class AccountAuthorizationConfigsController < ApplicationController
# @example_request
# # update SAML config
# curl -XPUT 'https://<canvas>/api/v1/accounts/<account_id>/account_authorization_configs/<id>' \
# -F 'idp_entity_id=<new_idp_entity_id>' \
# -F 'log_in_url=<new_url>' \
# -F 'idp_entity_id=<new_idp_entity_id>' \
# -F 'log_in_url=<new_url>' \
# -H 'Authorization: Bearer <token>'
#
# @returns AccountAuthorizationConfig
@ -494,14 +509,14 @@ class AccountAuthorizationConfigsController < ApplicationController
# If you have multiple IdPs configured, you can set a `discovery_url`.
# If that is set, canvas will forward all users to that URL when they need to
# be authenticated. That page will need to then help the user figure out where
# they need to go to log in.
# they need to go to log in.
#
# If no discovery url is configured, the 1st auth config will be used to
# If no discovery url is configured, the 1st auth config will be used to
# attempt to authenticate the user.
#
# @example_request
# curl -XPUT 'https://<canvas>/api/v1/accounts/<account_id>/account_authorization_configs/discovery_url' \
# -F 'discovery_url=<new_url>' \
# -F 'discovery_url=<new_url>' \
# -H 'Authorization: Bearer <token>'
#
# @returns DiscoveryUrl
@ -521,7 +536,7 @@ class AccountAuthorizationConfigsController < ApplicationController
# @API Delete discovery url
# Clear discovery url
#
#
# @example_request
# curl -XDELETE 'https://<canvas>/api/v1/accounts/<account_id>/account_authorization_configs/discovery_url' \
# -H 'Authorization: Bearer <token>'
@ -608,7 +623,7 @@ class AccountAuthorizationConfigsController < ApplicationController
end
redirect_to :account_account_authorization_configs
end
def saml_testing
if @account.saml_authentication?
@account_config = @account.account_authorization_config
@ -625,13 +640,13 @@ class AccountAuthorizationConfigsController < ApplicationController
end
end
end
def saml_testing_stop
if @account_config = @account.account_authorization_config
@account_config.finish_debugging
end
render :json => {:status => "ok"}
if @account_config = @account.account_authorization_config
@account_config.finish_debugging
end
render :json => {:status => "ok"}
end
protected

View File

@ -78,10 +78,11 @@ class PseudonymSessionsController < ApplicationController
successful_login(@user, @pseudonym)
return
else
logger.warn "Received CAS login for unknown user: #{st.user}"
unknown_user_url = @domain_root_account.account_authorization_config.unknown_user_url || cas_login_url(:no_auto=>'true')
logger.warn "Received CAS login for unknown user: #{st.user}, redirecting to: #{unknown_user_url}."
reset_session
session[:delegated_message] = t 'errors.no_matching_user', "Canvas doesn't have an account for user: %{user}", :user => st.user
redirect_to(cas_client.logout_url(cas_login_url :no_auto => true))
flash[:delegated_message] = t 'errors.no_matching_user', "Canvas doesn't have an account for user: %{user}", :user => st.user
redirect_to unknown_user_url
return
end
else
@ -379,13 +380,13 @@ class PseudonymSessionsController < ApplicationController
successful_login(@user, @pseudonym)
else
unknown_user_url = aac.unknown_user_url || login_url(:no_auto => 'true')
increment_saml_stat("errors.unknown_user")
message = "Received SAML login request for unknown user: #{unique_id}"
message = "Received SAML login request for unknown user: #{unique_id} redirecting to: #{unknown_user_url}."
logger.warn message
aac.debug_set(:canvas_login_fail_message, message) if debugging
# the saml message has to survive a couple redirects
session[:delegated_message] = t 'errors.no_matching_user', "Canvas doesn't have an account for user: %{user}", :user => unique_id
logout_user_action
flash[:delegated_message] = t 'errors.no_matching_user', "Canvas doesn't have an account for user: %{user}", :user => unique_id
redirect_to unknown_user_url
end
elsif response.auth_failure?
increment_saml_stat("normal.login_failure")

View File

@ -29,11 +29,11 @@ class AccountAuthorizationConfig < ActiveRecord::Base
acts_as_list :scope => :account
attr_accessible :account, :auth_port, :auth_host, :auth_base, :auth_username,
:auth_password, :auth_password_salt, :auth_type, :auth_over_tls,
:log_in_url, :log_out_url, :identifier_format,
:certificate_fingerprint, :entity_id, :change_password_url,
:login_handle_name, :ldap_filter, :auth_filter, :requested_authn_context,
:login_attribute, :idp_entity_id
:auth_password, :auth_password_salt, :auth_type, :auth_over_tls,
:log_in_url, :log_out_url, :identifier_format,
:certificate_fingerprint, :entity_id, :change_password_url,
:login_handle_name, :ldap_filter, :auth_filter, :requested_authn_context,
:login_attribute, :idp_entity_id, :unknown_user_url
before_validation :set_saml_defaults, :if => Proc.new { |aac| aac.saml_authentication? }
@ -51,7 +51,7 @@ class AccountAuthorizationConfig < ActiveRecord::Base
def self.recognized_params(auth_type)
case auth_type
when 'cas'
[ :auth_type, :auth_base, :log_in_url, :login_handle_name ]
[ :auth_type, :auth_base, :log_in_url, :login_handle_name, :unknown_user_url ]
when 'ldap'
[ :auth_type, :auth_host, :auth_port, :auth_over_tls, :auth_base,
:auth_filter, :auth_username, :auth_password, :change_password_url,
@ -59,7 +59,7 @@ class AccountAuthorizationConfig < ActiveRecord::Base
when 'saml'
[ :auth_type, :log_in_url, :log_out_url, :change_password_url, :requested_authn_context,
:certificate_fingerprint, :identifier_format, :login_handle_name,
:login_attribute, :idp_entity_id, :position]
:login_attribute, :idp_entity_id, :position, :unknown_user_url ]
else
[]
end
@ -67,14 +67,14 @@ class AccountAuthorizationConfig < ActiveRecord::Base
def self.auth_over_tls_setting(value)
case value
when nil, '', false, 'false', 'f', 0, '0'
nil
when true, 'true', 't', 1, '1', 'simple_tls', :simple_tls
'simple_tls'
when 'start_tls', :start_tls
'start_tls'
else
raise ArgumentError("invalid auth_over_tls setting: #{value}")
when nil, '', false, 'false', 'f', 0, '0'
nil
when true, 'true', 't', 1, '1', 'simple_tls', :simple_tls
'simple_tls'
when 'start_tls', :start_tls
'start_tls'
else
raise ArgumentError("invalid auth_over_tls setting: #{value}")
end
end
@ -92,12 +92,12 @@ class AccountAuthorizationConfig < ActiveRecord::Base
ldap.auth self.auth_username, self.auth_decrypted_password
ldap
end
def set_saml_defaults
self.entity_id ||= saml_default_entity_id
self.requested_authn_context = nil if self.requested_authn_context.blank?
end
def self.saml_login_attributes
{
'NameID' => 'nameid',
@ -105,24 +105,24 @@ class AccountAuthorizationConfig < ActiveRecord::Base
t(:saml_eppn_domain_stripped, "%{eppn} (domain stripped)", :eppn => "eduPersonPrincipalName") =>'eduPersonPrincipalName_stripped'
}
end
def sanitized_ldap_login(login)
[ [ '\\', '\5c' ], [ '*', '\2a' ], [ '(', '\28' ], [ ')', '\29' ], [ "\00", '\00' ] ].each do |re|
login.gsub!(re[0], re[1])
end
login
end
def ldap_filter(login = nil)
filter = self.auth_filter
filter = filter.gsub(/\{\{login\}\}/, sanitized_ldap_login(login)) if login
filter
end
def change_password_url
read_attribute(:change_password_url).blank? ? nil : read_attribute(:change_password_url)
end
def ldap_filter=(new_filter)
self.auth_filter = new_filter
end
@ -134,21 +134,21 @@ class AccountAuthorizationConfig < ActiveRecord::Base
return nil
end
end
def auth_password=(password)
return if password.nil? or password == ''
self.auth_crypted_password, self.auth_password_salt = Canvas::Security.encrypt_password(password, 'instructure_auth')
end
def auth_decrypted_password
return nil unless self.auth_password_salt && self.auth_crypted_password
Canvas::Security.decrypt_password(self.auth_crypted_password, self.auth_password_salt, 'instructure_auth')
end
def self.saml_default_entity_id_for_account(account)
"http://#{HostUrl.context_host(account)}/saml2"
end
def saml_default_entity_id
AccountAuthorizationConfig.saml_default_entity_id_for_account(self.account)
end
@ -170,26 +170,26 @@ class AccountAuthorizationConfig < ActiveRecord::Base
@saml_settings.name_identifier_format = self.identifier_format
@saml_settings.requested_authn_context = self.requested_authn_context
end
@saml_settings
end
def self.saml_settings_for_account(account, current_host=nil)
app_config = ConfigFile.load('saml') || {}
domains = HostUrl.context_hosts(account, current_host)
settings = Onelogin::Saml::Settings.new
settings.sp_slo_url = "#{HostUrl.protocol}://#{domains.first}/saml_logout"
settings.assertion_consumer_service_url = domains.map { |domain| "#{HostUrl.protocol}://#{domain}/saml_consume" }
settings.tech_contact_name = app_config[:tech_contact_name] || 'Webmaster'
settings.tech_contact_email = app_config[:tech_contact_email] || ''
if account.saml_authentication?
settings.issuer = account.account_authorization_config.entity_id
else
settings.issuer = saml_default_entity_id_for_account(account)
end
encryption = app_config[:encryption]
if encryption.is_a?(Hash)
settings.xmlsec_certificate = resolve_saml_key_path(encryption[:certificate])
@ -197,7 +197,7 @@ class AccountAuthorizationConfig < ActiveRecord::Base
settings.xmlsec_additional_privatekeys = Array(encryption[:additional_private_keys]).map { |apk| resolve_saml_key_path(apk) }.compact
end
settings
end
@ -212,15 +212,15 @@ class AccountAuthorizationConfig < ActiveRecord::Base
path.exist? ? path.to_s : nil
end
def email_identifier?
if self.saml_authentication?
return self.identifier_format == Onelogin::Saml::NameIdentifiers::EMAIL
end
false
end
def password_authentication?
!['cas', 'ldap', 'saml'].member?(self.auth_type)
end
@ -232,23 +232,23 @@ class AccountAuthorizationConfig < ActiveRecord::Base
def cas_authentication?
self.auth_type == 'cas'
end
def ldap_authentication?
self.auth_type == 'ldap'
end
def saml_authentication?
self.auth_type == 'saml'
end
def self.default_login_handle_name
t(:default_login_handle_name, "Email")
end
def self.default_delegated_login_handle_name
t(:default_delegated_login_handle_name, "Login")
end
def self.serialization_excludes; [:auth_crypted_password, :auth_password_salt]; end
def test_ldap_connection
@ -336,38 +336,38 @@ class AccountAuthorizationConfig < ActiveRecord::Base
def debugging?
!!Rails.cache.fetch(debug_key(:debugging))
end
def debugging_keys
[:debugging, :request_id, :to_idp_url, :to_idp_xml, :idp_response_encoded,
[:debugging, :request_id, :to_idp_url, :to_idp_xml, :idp_response_encoded,
:idp_in_response_to, :fingerprint_from_idp, :idp_response_xml_encrypted,
:idp_response_xml_decrypted, :idp_login_destination, :is_valid_login_response,
:login_response_validation_error, :login_to_canvas_success, :canvas_login_fail_message,
:logged_in_user_id, :logout_request_id, :logout_to_idp_url, :logout_to_idp_xml,
:idp_logout_response_encoded, :idp_logout_in_response_to,
:login_response_validation_error, :login_to_canvas_success, :canvas_login_fail_message,
:logged_in_user_id, :logout_request_id, :logout_to_idp_url, :logout_to_idp_xml,
:idp_logout_response_encoded, :idp_logout_in_response_to,
:idp_logout_response_xml_encrypted, :idp_logout_destination]
end
def finish_debugging
debugging_keys.each { |key| Rails.cache.delete(debug_key(key)) }
end
def start_debugging
finish_debugging # clear old data
debug_set(:debugging, t('debug.wait_for_login', "Waiting for attempted login"))
end
def debug_get(key)
Rails.cache.fetch(debug_key(key))
end
def debug_set(key, value)
Rails.cache.write(debug_key(key), value, :expires_in => debug_expire)
end
def debug_key(key)
['aac_debugging', self.id, key.to_s].cache_key
end
def debug_expire
Setting.get('aac_debug_expire_minutes', 30).minutes
end
@ -384,10 +384,10 @@ class AccountAuthorizationConfig < ActiveRecord::Base
Canvas.timeout_protection("ldap:#{self.global_id}",
raise_on_timeout: true,
fallback_timeout_length: default_timeout) do
ldap = self.ldap_connection
filter = self.ldap_filter(unique_id)
ldap.bind_as(:base => ldap.base, :filter => filter, :password => password_plaintext)
end
ldap = self.ldap_connection
filter = self.ldap_filter(unique_id)
ldap.bind_as(:base => ldap.base, :filter => filter, :password => password_plaintext)
end
rescue => e
ErrorReport.log_exception(:ldap, e, :account => self.account)
if e.is_a?(Timeout::Error)

View File

@ -34,6 +34,16 @@
</span>
</td>
</tr>
<tr>
<td style="vertical-align: top; width: 200px;"><%= f.blabel :unknown_user_url, :en => "Unknown User Url" %></td>
<td style="vertical-align: top;" class="nobr">
<%= f.text_field :unknown_user_url, :class => "auth_form", :style => "width: 450px;", :placeholder => cas_login_url(:no_auto=>'true') %>
<span class="auth_info auth_unknown_user_url"><%= @account_config.unknown_user_url %></span>
<span class="auth_form" style="font-size: smaller;">
<br><%= t(:unknown_user_url_description, "The url to redirect to when an authenticated user is not found in Canvas.") %>
</span>
</td>
</tr>
<tr>
<td colspan="4">
<div class="form-actions">

View File

@ -203,6 +203,14 @@
<p class="help-block"><%= t(:login_handle_name_description, "The label used for unique login identifiers. Examples: Login, Username, Student ID, etc.") %></p>
</div>
</div>
<div class="control-group">
<label class="control-label" for="input01"><%= f.blabel :unkown_user_url, :en => "Unknown User Url" %></label>
<div class="controls">
<%= f.text_field :unknown_user_url, :class => "input-xlarge", :placeholder => login_url(:no_auto=>'true') %>
<p class="help-block"><%= t(:unkown_user_url_description, "The url to redirect to when an authenticated user is not found in Canvas.") %></p>
</div>
</div>
<% if @saml_configs.length > 1 %>
<div class="control-group">
<label class="control-label" for="input01"><%= f.blabel :position, :en => "Position" %></label>

View File

@ -0,0 +1,7 @@
class AddUnknownUserUrlToAccountAuthorizationConfig < ActiveRecord::Migration
tag :predeploy
def change
add_column :account_authorization_configs, :unknown_user_url, :string
end
end

View File

@ -203,6 +203,7 @@ describe "AccountAuthorizationConfigs API", type: :request do
@saml_hash['change_password_url'] = nil
@saml_hash['requested_authn_context'] = nil
@saml_hash['login_attribute'] = 'nameid'
@saml_hash['unknown_user_url'] = nil
json.should == @saml_hash
end
@ -230,6 +231,7 @@ describe "AccountAuthorizationConfigs API", type: :request do
@cas_hash['log_in_url'] = nil
@cas_hash['id'] = aac.id
@cas_hash['position'] = 1
@cas_hash['unknown_user_url'] = nil
json.should == @cas_hash
end

View File

@ -250,6 +250,33 @@ describe PseudonymSessionsController do
Pseudonym.find(session['pseudonym_credentials_id']).should == user2.pseudonyms.first
end
it "should redirect when a user is authenticted but is not found in canvas" do
ConfigFile.stub('saml', {})
unique_id = 'foo@example.com'
account = account_with_saml
controller.stubs(:saml_response).returns(
stub('response', :is_valid? => true, :success_status? => true, :name_id => unique_id, :name_qualifier => nil, :session_index => nil, :process => nil)
)
# We dont want to log them out of everything.
controller.expects(:logout_user_action).never
controller.request.env['canvas.domain_root_account'] = account
# Default to Login url
get 'saml_consume', :SAMLResponse => "foo"
response.should redirect_to(login_url(:no_auto => 'true'))
session[:saml_unique_id].should be_nil
# Redirect to a specifiec url
unknown_user_url = "https://example.com/unknown_user"
account.account_authorization_config.unknown_user_url = unknown_user_url
get 'saml_consume', :SAMLResponse => "foo"
response.should redirect_to(unknown_user_url)
session[:saml_unique_id].should be_nil
end
context "multiple authorization configs" do
before :once do
@account = Account.create!
@ -760,6 +787,29 @@ describe PseudonymSessionsController do
Pseudonym.find(session['pseudonym_credentials_id']).should == user2.pseudonyms.first
end
it "should redirect when a user is authorized but not found in canvas" do
unique_id = 'foo@example.com'
account = account_with_cas
stubby("yes\n#{unique_id}\n")
# We dont want to log them out of everything.
controller.expects(:logout_user_action).never
controller.request.env['canvas.domain_root_account'] = account
# Default to Login url
get 'new', :ticket => 'ST-abcd'
response.should redirect_to(cas_login_url(:no_auto => 'true'))
session[:cas_session].should be_nil
# Redirect to a specific url
unknown_user_url = "https://example.com/unknown_user"
account.account_authorization_config.unknown_user_url = unknown_user_url
get 'new', :ticket => 'ST-abcd'
response.should redirect_to(unknown_user_url)
session[:cas_session].should be_nil
end
it "should log out correctly if the user is from a different account" do
account = account_with_cas
user_with_pseudonym(active_all: true, account: account)

View File

@ -98,11 +98,26 @@ describe PseudonymSessionsController do
redirect_until(@cas_client.add_service_to_login_url(cas_login_url))
get cas_login_url :ticket => 'ST-abcd'
response.should redirect_to(@cas_client.logout_url(cas_login_url :no_auto => true))
response.should redirect_to(cas_login_url(:no_auto => true))
get cas_login_url :no_auto => true
flash[:delegated_message].should match(/Canvas doesn't have an account for user/)
end
it "should redirect to a custom url if the user CAS account doesn't exist" do
redirect_url = login_url(:no_auto => 'true')
aac = Account.default.account_authorization_config
aac.unknown_user_url = redirect_url
aac.save
stubby("yes\nnonexistentuser\n")
get login_url
redirect_until(@cas_client.add_service_to_login_url(cas_login_url))
get cas_login_url :ticket => 'ST-abcd'
response.should redirect_to(redirect_url)
end
it "should login case insensitively" do
user = user_with_pseudonym({:active_all => true})

View File

@ -120,6 +120,38 @@ describe "account" do
dialog.find_element(:css, 'label[for="pseudonym_unique_id"]').text.should == "CAS Username:*"
end
context "cas" do
it "should be able to set unknown user url option" do
get "/accounts/#{Account.default.id}/account_authorization_configs"
click_option('#add_auth_select', 'cas', :value)
f("#account_authorization_config_0_login_handle_name").should be_displayed
unknown_user_url = 'https://example.com/unknown_user'
f("#account_authorization_config_0_unknown_user_url").send_keys(unknown_user_url)
expect_new_page_load { submit_form('#auth_form') }
Account.default.account_authorization_configs.first.unknown_user_url.should == unknown_user_url
end
end
context "saml" do
it "should be able to set unknown user url option" do
get "/accounts/#{Account.default.id}/account_authorization_configs"
click_option('#add_auth_select', 'saml', :value)
saml_div = f('#saml_div')
saml_div.find_element(:css, 'button.element_toggler.btn').click
f("#account_authorization_config_idp_entity_id").should be_displayed
unknown_user_url = 'https://example.com/unknown_user'
f("#account_authorization_config_unknown_user_url").send_keys(unknown_user_url)
expect_new_page_load { submit_form('#saml_config__form') }
Account.default.account_authorization_configs.first.unknown_user_url.should == unknown_user_url
end
end
it "should be able to create a new course" do
get "/accounts/#{Account.default.id}"
f('.add_course_link').click