configure SAML via metadata URI

fixes CNVS-28890, CNVS-28891

including auto-refresh, and optimizations for InCommon

test plan:
 * use a metadata URI to configure a SAML provider;
   everything should populate
 * have your IdP change their metadata, then wait a day
   for the periodic job, or manually run
   AccountAuthorizationConfig::SAML::MetadataRefresher.refresh_providers
 * it should be updated automatically
 * set the URI to urn:mace:incommon, and fill in a entity id
   of a school in InCommon
 * it should populate automatically

Change-Id: Ie508483da2ffa81dce3b98dbd5ae5c0b9e2ac878
Reviewed-on: https://gerrit.instructure.com/76989
QA-Review: Jeremy Putnam <jeremyp@instructure.com>
Reviewed-by: Rob Orton <rob@instructure.com>
Tested-by: Jenkins
Product-Review: Cody Cutrer <cody@instructure.com>
This commit is contained in:
Cody Cutrer 2016-04-12 14:32:04 -06:00
parent f71a829b47
commit 7db873f5eb
14 changed files with 519 additions and 18 deletions

View File

@ -89,8 +89,8 @@ gem 'rotp', '1.6.1', require: false
gem 'net-ldap', '0.10.1', require: false
gem 'ruby-duration', '3.2.0', require: false
gem 'ruby-saml-mod', '0.3.0'
gem 'saml2', '1.0.6', require: false
gem 'nokogiri-xmlsec-me-harder', '0.9.3pre', require: false, github: 'instructure/nokogiri-xmlsec-me-harder', ref: 'a39924b7483aee45171c77cb1110dd92de5c7bbe'
gem 'saml2', '1.0.7', require: false
gem 'nokogiri-xmlsec-me-harder', '0.9.3pre', require: false, github: 'instructure/nokogiri-xmlsec-me-harder', ref: '3236a249986413c6c399ef3477132a0af0410bb7'
gem 'rubycas-client', '2.3.9', require: false
gem 'rubyzip', '1.1.1', require: 'zip'
gem 'safe_yaml', '1.0.4', require: false

View File

@ -441,6 +441,17 @@ class AccountAuthorizationConfigsController < ApplicationController
# An XML document to parse as SAML metadata, and automatically populate idp_entity_id,
# log_in_url, log_out_url, certificate_fingerprint, and identifier_format
#
# - metadata_uri [Optional]
#
# A URI to download the SAML metadata from, and automatically populate idp_entity_id,
# log_in_url, log_out_url, certificate_fingerprint, and identifier_format. This URI
# will also be saved, and the metadata periodically refreshed, automatically. If
# the metadata contains multiple entities, also supply idp_entity_id to distinguish
# which one you want (otherwise the only entity in the metadata will be inferred).
# If you provide the URI 'urn:mace:incommon', the InCommon metadata aggregate will
# be used instead, and additional validation checks will happen (including
# validating that the metadata has been properly signed with the InCommon key).
#
# - idp_entity_id
#
# The SAML IdP's entity ID
@ -557,10 +568,19 @@ class AccountAuthorizationConfigsController < ApplicationController
end
update_deprecated_account_settings_data(aac_data, account_config)
unless account_config.save
respond_to do |format|
format.html do
flash[:error] = account_config.errors.full_messages
redirect_to(account_authentication_providers_path(@account))
end
format.json { raise ActiveRecord::RecordInvalid.new(account_config) }
end
return
end
if position.present?
account_config.insert_at(position.to_i)
else
account_config.save!
end
respond_to do |format|
@ -599,7 +619,18 @@ class AccountAuthorizationConfigsController < ApplicationController
end
deselect_parent_registration(data, aac)
aac.update_attributes(data)
aac.assign_attributes(data)
unless aac.save
respond_to do |format|
format.html do
flash[:error] = aac.errors.full_messages
redirect_to(account_authentication_providers_path(@account))
end
format.json { raise ActiveRecord::RecordInvalid.new(account_config) }
end
return
end
if position.present?
aac.insert_at(position.to_i)

View File

@ -44,7 +44,8 @@ class AccountAuthorizationConfig::SAML < AccountAuthorizationConfig::Delegated
:idp_entity_id,
:parent_registration,
:jit_provisioning,
:metadata
:metadata,
:metadata_uri
].freeze
end
@ -55,6 +56,7 @@ class AccountAuthorizationConfig::SAML < AccountAuthorizationConfig::Delegated
SENSITIVE_PARAMS = [:metadata].freeze
before_validation :set_saml_defaults
before_validation :download_metadata
validates_presence_of :entity_id
def auth_provider_filter
@ -70,6 +72,40 @@ class AccountAuthorizationConfig::SAML < AccountAuthorizationConfig::Delegated
self.requested_authn_context = nil if self.requested_authn_context.blank?
end
def download_metadata
return unless metadata_uri.present?
return unless metadata_uri_changed? || idp_entity_id_changed?
# someone's trying to cheat; switch to our more efficient implementation
self.metadata_uri = InCommon::URN if metadata_uri == InCommon.endpoint
if metadata_uri == InCommon::URN
unless idp_entity_id.present?
errors.add(:idp_entity_id, :present)
return
end
begin
entity = InCommon.metadata[idp_entity_id]
unless entity
errors.add(:idp_entity_id, t("Entity %{entity_id} not found in InCommon Metadata", entity_id: idp_entity_id))
return
end
populate_from_metadata(entity)
rescue => e
::Canvas::Errors.capture_exception(:incommon, e)
errors.add(:metadata_uri, e.message)
end
return
end
begin
populate_from_metadata_url(metadata_uri)
rescue => e
::Canvas::Errors.capture_exception(:saml_metadata_refresh, e)
errors.add(:metadata_uri, e.message)
end
end
def self.login_attributes
{
'NameID' => 'nameid',
@ -104,18 +140,23 @@ class AccountAuthorizationConfig::SAML < AccountAuthorizationConfig::Delegated
def populate_from_metadata_xml(xml)
entity = SAML2::Entity.parse(xml)
raise "Invalid schema" unless entity.valid_schema?
if entity.is_a?(SAML2::Entity::Group) && idp_entity_id.present?
entity = entity.find { |e| e.entity_id == idp_entity_id }
end
raise "Must be a single Entity" unless entity.is_a?(SAML2::Entity)
populate_from_metadata(entity)
end
alias_method :metadata=, :populate_from_metadata_xml
def populate_from_metadata_url(url)
response = ::Canvas.timeout_protection("saml_metadata_fetch") do
CanvasHttp.get(url)
::Canvas.timeout_protection("saml_metadata_fetch") do
CanvasHttp.get(url) do |response|
# raise error unless it's a 2xx
response.value
populate_from_metadata_xml(response.body)
end
end
# raise error unless it's a 2xx
response.value
populate_from_metadata_xml(response.body)
end
def saml_settings(current_host=nil)

View File

@ -0,0 +1,97 @@
#
# Copyright (C) 2013 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 'saml2'
class AccountAuthorizationConfig::SAML::InCommon < AccountAuthorizationConfig::SAML::MetadataRefresher
URN = 'urn:mace:incommon'.freeze
class << self
def metadata
if Canvas.redis_enabled?
deflated = Canvas.redis.get('incommon_metadata')
existing_data = Zlib::Inflate.inflate(deflated) if deflated
end
new_data = refresh_if_necessary('incommon', endpoint, force_fetch: !existing_data)
validate_and_parse_metadata(new_data || existing_data)
end
def refresh_providers(shard_scope: Shard.in_current_region, providers: nil)
providers ||= AccountAuthorizationConfig::SAML.active.
where(metadata_uri: URN).shard(shard_scope)
# don't even bother checking InCommon if no one is using it
# (but a multi-shard environment probably is, and it's expensive
# to check them all, so just check InCommon)
return if Shard.count <= 1 && !providers.exists?
new_data = refresh_if_necessary('incommon', endpoint)
# no changes; don't bother with the hard work
return unless new_data
metadata = validate_and_parse_metadata(new_data)
providers.each do |provider|
entity = metadata[provider.idp_entity_id]
unless entity
::Canvas::Errors.capture_exception(:incommon,
"Entity #{provider.idp_entity_id} not found in InCommon metadata")
next
end
begin
provider.populate_from_metadata(entity)
provider.save! if provider.changed?
rescue => e
::Canvas::Errors.capture_exception(:incommon, e)
end
end
end
def endpoint
Setting.get('incommon_metadata_url', 'http://md.incommon.org/InCommon/InCommon-metadata.xml')
end
private
def validate_and_parse_metadata(xml)
entities = SAML2::Entity.parse(xml)
raise "Expected a group of entities" unless entities.is_a?(SAML2::Entity::Group)
raise "Invalid XML" unless entities.valid_schema?
unless entities.valid_until && entities.valid_until > Time.now.utc
raise "Problem with validUntil: #{entities.valid_until}"
end
raise "Not signed!" unless entities.signed?
unless entities.valid_signature?(cert: Rails.root.join("config/saml/inc-md-cert.pem").read)
raise "Invalid signature!"
end
entities.index_by(&:entity_id)
end
def refresh_if_necessary(*args)
result = super
# save the new data if there is any
if Canvas.redis_enabled? && result
Canvas.redis.set('incommon_metadata', Zlib::Deflate.deflate(result, 9))
end
result
end
end
end

View File

@ -0,0 +1,64 @@
#
# Copyright (C) 2013 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 'saml2'
class AccountAuthorizationConfig::SAML::MetadataRefresher
class << self
def refresh_providers(shard_scope: Shard.current, providers: nil)
providers ||= AccountAuthorizationConfig::SAML.active.
where.not(metadata_uri: [nil, AccountAuthorizationConfig::SAML::InCommon::URN]).
shard(shard_scope)
providers.each do |provider|
begin
new_data = refresh_if_necessary(provider.global_id, provider.metadata_uri)
next unless new_data
provider.populate_from_metadata_xml(new_data)
provider.save! if provider.changed?
rescue => e
::Canvas::Errors.capture_exception(:saml_metadata_refresh, e)
end
end
end
protected
# returns the new data if it changed, or false if it has not
def refresh_if_necessary(provider_key, endpoint, force_fetch: false)
if !force_fetch && Canvas.redis_enabled?
etag = Canvas.redis.get("saml_#{provider_key}_etag")
end
headers = {}
headers['If-None-Match'] = etag if etag
CanvasHttp.get(endpoint, headers) do |response|
if response.is_a?(Net::HTTPNotModified)
return false
end
# raise on non-success
response.value
# store new data
if Canvas.redis_enabled? && response['ETag']
Canvas.redis.set("saml_#{provider_key}_etag", response['ETag'])
end
return response.body
end
end
end
end

View File

@ -4,6 +4,16 @@ is available at that URL.
TEXT
%></p>
<% css_bundle :saml_fields %>
<p><%= t(<<-TEXT)
Provide a URI to your IdP's metadata to automatically populate the other
fields. If your school is part of InCommon, specify urn:mace:incommon for
the metadata URI, and also provide your school's entity ID.
TEXT
%></p>
<div class="ic-Form-control">
<%= f.label :metadata_uri, t('IdP Metadata URI'), class: 'ic-Label' %>
<%= f.text_field :metadata_uri, class: 'ic-Input' %>
</div>
<div class="ic-Form-control">
<%= f.label :idp_entity_id, t('IdP Entity ID'), class: 'ic-Label' %>
<%= f.text_field :idp_entity_id, class: 'ic-Input' %>

View File

@ -134,4 +134,17 @@ Rails.configuration.after_initialize do
Delayed::Periodic.cron 'Version::Partitioner.process', '0 0 * * *' do
with_each_shard_by_database(Version::Partitioner, :process)
end
if AccountAuthorizationConfig::SAML.enabled?
Delayed::Periodic.cron 'AccountAuthorizationConfig::SAML::MetadataRefresher.refresh_providers', '15 0 * * *' do
with_each_shard_by_database(AccountAuthorizationConfig::SAML::MetadataRefresher,
:refresh_providers)
end
Delayed::Periodic.cron 'AccountAuthorizationConfig::SAML::InCommon.refresh_providers', '45 0 * * *' do
DatabaseServer.send_in_each_region(AccountAuthorizationConfig::SAML::InCommon,
:refresh_providers,
singleton: 'AccountAuthorizationConfig::SAML::InCommon.refresh_providers')
end
end
end

View File

@ -0,0 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIDgTCCAmmgAwIBAgIJAJRJzvdpkmNaMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNV
BAYTAlVTMRUwEwYDVQQKDAxJbkNvbW1vbiBMTEMxMTAvBgNVBAMMKEluQ29tbW9u
IEZlZGVyYXRpb24gTWV0YWRhdGEgU2lnbmluZyBLZXkwHhcNMTMxMjE2MTkzNDU1
WhcNMzcxMjE4MTkzNDU1WjBXMQswCQYDVQQGEwJVUzEVMBMGA1UECgwMSW5Db21t
b24gTExDMTEwLwYDVQQDDChJbkNvbW1vbiBGZWRlcmF0aW9uIE1ldGFkYXRhIFNp
Z25pbmcgS2V5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0Chdkrn+
dG5Zj5L3UIw+xeWgNzm8ajw7/FyqRQ1SjD4Lfg2WCdlfjOrYGNnVZMCTfItoXTSp
g4rXxHQsykeNiYRu2+02uMS+1pnBqWjzdPJE0od+q8EbdvE6ShimjyNn0yQfGyQK
CNdYuc+75MIHsaIOAEtDZUST9Sd4oeU1zRjV2sGvUd+JFHveUAhRc0b+JEZfIEuq
/LIU9qxm/+gFaawlmojZPyOWZ1JlswbrrJYYyn10qgnJvjh9gZWXKjmPxqvHKJcA
TPhAh2gWGabWTXBJCckMe1hrHCl/vbDLCmz0/oYuoaSDzP6zE9YSA/xCplaHA0mo
C1Vs2H5MOQGlewIDAQABo1AwTjAdBgNVHQ4EFgQU5ij9YLU5zQ6K75kPgVpyQ2N/
lPswHwYDVR0jBBgwFoAU5ij9YLU5zQ6K75kPgVpyQ2N/lPswDAYDVR0TBAUwAwEB
/zANBgkqhkiG9w0BAQsFAAOCAQEAaQkEx9xvaLUt0PNLvHMtxXQPedCPw5xQBd2V
WOsWPYspRAOSNbU1VloY+xUkUKorYTogKUY1q+uh2gDIEazW0uZZaQvWPp8xdxWq
Dh96n5US06lszEc+Lj3dqdxWkXRRqEbjhBFh/utXaeyeSOtaX65GwD5svDHnJBcl
AGkzeRIXqxmYG+I2zMm/JYGzEnbwToyC7yF6Q8cQxOr37hEpqz+WN/x3qM2qyBLE
CQFjmlJrvRLkSL15PCZiu+xFNFd/zx6btDun5DBlfDS9DG+SHCNH6Nq+NfP+ZQ8C
GzP/3TaZPzMlKPDCjp0XOQfyQqFIXdwjPFTWjEusDBlm4qJAlQ==
-----END CERTIFICATE-----

View File

@ -0,0 +1,8 @@
class AddMetadataUriToAuthenticationProviders < ActiveRecord::Migration
tag :predeploy
def change
add_column :account_authorization_configs, :metadata_uri, :string
add_index :account_authorization_configs, :metadata_uri, where: "metadata_uri IS NOT NULL"
end
end

View File

@ -57,16 +57,17 @@ module CanvasHttp
request = request_class.new(uri.request_uri, other_headers)
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
http.request(request) do |response|
case response
when Net::HTTPRedirection
last_host = uri.host
last_scheme = uri.scheme
url_str = response['Location']
redirect_limit -= 1
else
if response.is_a?(Net::HTTPRedirection) && !response.is_a?(Net::HTTPNotModified)
last_host = uri.host
last_scheme = uri.scheme
url_str = response['Location']
redirect_limit -= 1
else
if block_given?
yield response
else
# have to read the body before we exit this block, and
# close the connection
response.body
end
return response

View File

@ -192,6 +192,7 @@ describe "AuthenticationProviders API", type: :request do
@saml_hash['unknown_user_url'] = nil
@saml_hash['parent_registration'] = false
@saml_hash['jit_provisioning'] = false
@saml_hash['metadata_uri'] = nil
expect(json).to eq @saml_hash
end

View File

@ -0,0 +1,86 @@
#
# Copyright (C) 2016 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_relative '../../../spec_helper'
describe AccountAuthorizationConfig::SAML::InCommon do
let(:subject) { AccountAuthorizationConfig::SAML::InCommon }
describe ".refresh_providers" do
before do
AccountAuthorizationConfig::SAML.any_instance.stubs(:download_metadata).returns(nil)
end
let!(:saml) { Account.default.authentication_providers.create!(auth_type: 'saml',
metadata_uri: subject::URN,
idp_entity_id: 'urn:mace:incommon:myschool.edu') }
it "does nothing if there aren't any InCommon providers" do
saml.destroy
subject.expects(:refresh_if_necessary).never
subject.refresh_providers
end
it "does nothing if no changes" do
subject.expects(:refresh_if_necessary).returns(false)
subject.expects(:validate_and_parse_metadata).never
subject.refresh_providers
end
it "records errors for missing metadata" do
subject.expects(:refresh_if_necessary).returns('xml')
subject.expects(:validate_and_parse_metadata).returns({})
Canvas::Errors.expects(:capture_exception).once
saml.any_instantiation.expects(:populate_from_metadata).never
subject.refresh_providers
end
it "continues after a failure" do
saml2 = Account.default.authentication_providers.create!(auth_type: 'saml',
metadata_uri: subject::URN,
idp_entity_id: 'urn:mace:incommon:myschool2.edu')
subject.expects(:refresh_if_necessary).returns('xml')
subject.expects(:validate_and_parse_metadata).returns({
'urn:mace:incommon:myschool.edu' => 'metadata1',
'urn:mace:incommon:myschool2.edu' => 'metadata2',
})
Canvas::Errors.expects(:capture_exception).once
saml.any_instantiation.expects(:populate_from_metadata).with('metadata1').raises('error')
saml2.any_instantiation.expects(:populate_from_metadata).with('metadata2')
saml2.any_instantiation.expects(:save!).never
subject.refresh_providers
end
it "populates and saves" do
subject.expects(:refresh_if_necessary).returns('xml')
subject.expects(:validate_and_parse_metadata).returns({
'urn:mace:incommon:myschool.edu' => 'metadata1'
})
saml.any_instantiation.expects(:populate_from_metadata).with('metadata1')
saml.any_instantiation.expects(:changed?).returns(true)
saml.any_instantiation.expects(:save!).once
subject.refresh_providers
end
end
end

View File

@ -0,0 +1,112 @@
#
# Copyright (C) 2016 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_relative '../../../spec_helper'
describe AccountAuthorizationConfig::SAML::MetadataRefresher do
let(:subject) { AccountAuthorizationConfig::SAML::MetadataRefresher }
describe ".refresh_providers" do
before do
AccountAuthorizationConfig::SAML.any_instance.stubs(:download_metadata).returns(nil)
end
let (:saml1) { Account.default.authentication_providers.create!(auth_type: 'saml', metadata_uri: '1') }
it "keeps going even if one fails" do
saml2 = Account.default.authentication_providers.create!(auth_type: 'saml', metadata_uri: '2')
subject.expects(:refresh_if_necessary).with(saml1.global_id, '1').raises('die')
subject.expects(:refresh_if_necessary).with(saml2.global_id, '2').returns(false)
::Canvas::Errors.expects(:capture_exception).once
subject.refresh_providers
end
it "doesn't populate if nothing changed" do
subject.expects(:refresh_if_necessary).with(saml1.global_id, '1').returns(false)
saml1.expects(:populate_from_metadata_xml).never
subject.refresh_providers
end
it "does populate, but doesn't save, if the XML changed, but nothing changes on the model" do
subject.expects(:refresh_if_necessary).with(saml1.global_id, '1').returns('xml')
saml1.any_instantiation.expects(:populate_from_metadata_xml).with('xml')
saml1.any_instantiation.expects(:save!).never
subject.refresh_providers
end
it "populates and saves" do
subject.expects(:refresh_if_necessary).with(saml1.global_id, '1').returns('xml')
saml1.any_instantiation.expects(:populate_from_metadata_xml).with('xml')
saml1.any_instantiation.expects(:changed?).returns(true)
saml1.any_instantiation.expects(:save!).once
subject.refresh_providers
end
end
describe ".refresh_if_necessary" do
let(:redis) { stub("redis") }
before do
Canvas.stubs(:redis_enabled?).returns(true)
Canvas.stubs(:redis).returns(redis)
end
it "passes ETag if we know it" do
redis.expects(:get).returns("MyETag")
CanvasHttp.expects(:get).with("url", "If-None-Match" => "MyETag")
subject.send(:refresh_if_necessary, 1, 'url')
end
it "doesn't pass ETag if force_fetch: true" do
redis.expects(:get).never
CanvasHttp.expects(:get).with("url", {})
subject.send(:refresh_if_necessary, 1, 'url', force_fetch: true)
end
it "returns false if not modified" do
redis.expects(:get).returns("MyETag")
response = stub("response")
response.expects(:is_a?).with(Net::HTTPNotModified).returns(true)
CanvasHttp.expects(:get).with("url", "If-None-Match" => "MyETag").yields(response)
expect(subject.send(:refresh_if_necessary, 1, 'url')).to eq false
end
it "sets the ETag if provided" do
redis.expects(:get).returns(nil)
response = stub("response")
response.expects(:is_a?).with(Net::HTTPNotModified).returns(false)
response.expects(:value)
response.stubs(:[]).with('ETag').returns("NewETag")
redis.expects(:set).with("saml_1_etag", "NewETag")
response.expects(:body).returns("xml")
CanvasHttp.expects(:get).with("url", {}).yields(response)
expect(subject.send(:refresh_if_necessary, 1, 'url')).to eq "xml"
end
end
end

View File

@ -110,6 +110,22 @@ describe AccountAuthorizationConfig::SAML do
@aac = @account.authentication_providers.create!(:auth_type => "saml", :requested_authn_context => "anything")
expect(@aac.requested_authn_context).to eq "anything"
end
describe "download_metadata" do
it 'requires an entity id for InCommon' do
saml = Account.default.authentication_providers.new(auth_type: 'saml',
metadata_uri: AccountAuthorizationConfig::SAML::InCommon::URN)
expect(saml).not_to be_valid
expect(saml.errors.first.first).to eq :idp_entity_id
end
it 'changes InCommon URI to the URN for it' do
saml = Account.default.authentication_providers.new(auth_type: 'saml',
metadata_uri: AccountAuthorizationConfig::SAML::InCommon.endpoint)
expect(saml).not_to be_valid
expect(saml.metadata_uri).to eq AccountAuthorizationConfig::SAML::InCommon::URN
end
end
end
describe '.resolve_saml_key_path' do