Show Account settings and edit MSFT Sync settings
Added an endpoint that allows you to view the specified accounts settings, as long as you're authorized. In addition, update the update_api action to allow updating Microsoft Teams Sync settings, namely :microsoft_sync_enabled, :microsoft_sync_tenant, and :microsoft_sync_login_attribute. closes INTEROP-6631, INTEROP-6630 test-plan: - Ensure you have an authorization token with AccountAdmin privileges or higher. - Using curl/Postman/Insomnia, try to enable Microsoft Sync with the feature flag disabled. Ensure a 400 is returned. - Enable the :microsoft_group_enrollments_syncing feature flag - Try to enable Microsoft Sync without a tenant or login_attribute and ensure a 400 is returned. Gotta have that info - Try to enable Microsoft Sync with an invalid login_attribute. Choose any random string at all. Ensure you get a 400. - try to enable Microsoft Sync without a valid tenant/domain name, such as "://$$$$$", or "invalidtenant-". Ensure a 400 is returned. - Enable Microsoft Sync with a valid tenant and login_attribute. Ensure a 200 is returned. - Try and modify settings as an unauthenticated user. You should get back a 401. - To test the show settings endpoint, make sure you have some account settings set. - Try to access the account settings as an unauthenticated user. Make sure you get a 401. - Access the account settings as an authorized user (Account Admin). Ensure that you get back a JSON object that represents all of your current account settings. flag = microsoft_group_enrollments_syncing Change-Id: Ib785987621e090a80ffa63fb48be3ed63243fe56 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/261189 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> QA-Review: Mysti Lilla <mysti@instructure.com> Product-Review: Ryan Hawkins <ryan.hawkins@instructure.com> Reviewed-by: Evan Battaglia <ebattaglia@instructure.com>
This commit is contained in:
parent
9ba2fd8b13
commit
1e214ae35e
|
@ -302,6 +302,7 @@ class AccountsController < ApplicationController
|
|||
include Api::V1::Account
|
||||
include CustomSidebarLinksHelper
|
||||
include SupportHelpers::ControllerHelpers
|
||||
include MicrosoftSync::Concerns::Settings
|
||||
|
||||
INTEGER_REGEX = /\A[+-]?\d+\z/
|
||||
SIS_ASSINGMENT_NAME_LENGTH_DEFAULT = 255
|
||||
|
@ -397,6 +398,22 @@ class AccountsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
# @API Settings
|
||||
# Returns all of the settings for the specified account as a JSON object. The caller must be an Account
|
||||
# admin with the manage_account_settings permission.
|
||||
#
|
||||
# @example_request
|
||||
# curl https://<canvas>/api/v1/accounts/<account_id>/settings \
|
||||
# -H 'Authorization: Bearer <token>'
|
||||
#
|
||||
# @example_response
|
||||
# {"usage_rights_required": true, "lock_all_announcements": true, "restrict_student_past_view": true}
|
||||
def show_settings
|
||||
return render_unauthorized_action unless @account.grants_right?(@current_user, session, :manage_account_settings)
|
||||
|
||||
render :json => @account.settings
|
||||
end
|
||||
|
||||
# @API Permissions
|
||||
# Returns permission information for the calling user and the given account.
|
||||
# You may use `self` as the account id to check permissions against the domain root account.
|
||||
|
@ -790,6 +807,13 @@ class AccountsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
# All the Microsoft Teams Sync things!
|
||||
sync_enabled = params.dig(:account, :settings)&.delete(:microsoft_sync_enabled)
|
||||
tenant = params.dig(:account, :settings)&.delete(:microsoft_sync_tenant)
|
||||
login_attribute = params.dig(:account, :settings)&.delete(:microsoft_sync_login_attribute)
|
||||
set_microsoft_sync_settings(sync_enabled, tenant, login_attribute)
|
||||
|
||||
|
||||
# quotas (:manage_account_quotas)
|
||||
quota_settings = account_params.slice(:default_storage_quota_mb, :default_user_storage_quota_mb,
|
||||
:default_group_storage_quota_mb)
|
||||
|
@ -870,6 +894,21 @@ class AccountsController < ApplicationController
|
|||
# @argument account[settings][restrict_student_future_view][value] [Boolean]
|
||||
# Restrict students from viewing courses before start date
|
||||
#
|
||||
# @argument account[settings][microsoft_sync_enabled] [Boolean]
|
||||
# Determines whether this account has Microsoft Teams Sync enabled or not.
|
||||
#
|
||||
# Note that if you are altering Microsoft Teams sync settings you must enable
|
||||
# the Microsoft Group enrollment syncing feature flag. In addition, if you are enabling
|
||||
# Microsoft Teams sync, you must also specify a tenant and login attribute.
|
||||
#
|
||||
# @argument account[settings][microsoft_sync_tenant]
|
||||
# The tenant this account should use when using Microsoft Teams Sync.
|
||||
# This should be an Azure Active Directory domain name.
|
||||
#
|
||||
# @argument account[settings][microsoft_sync_login_attribute]
|
||||
# The attribute this account should use to lookup users when using Microsoft Teams Sync.
|
||||
# Must be one of sub, email, oid, or preferred_username.
|
||||
#
|
||||
# @argument account[settings][restrict_student_future_view][locked] [Boolean]
|
||||
# Lock this setting for sub-accounts and courses
|
||||
#
|
||||
|
@ -1624,10 +1663,11 @@ class AccountsController < ApplicationController
|
|||
:include_students_in_global_survey, :license_type,
|
||||
{:lock_all_announcements => [:value, :locked]}.freeze,
|
||||
:login_handle_name, :mfa_settings, :no_enrollments_can_create_courses,
|
||||
:mobile_qr_login_is_enabled, :open_registration,
|
||||
:outgoing_email_default_name, :prevent_course_renaming_by_teachers,
|
||||
:prevent_course_availability_editing_by_teachers, :restrict_quiz_questions,
|
||||
{:restrict_student_future_listing => [:value, :locked].freeze}.freeze,
|
||||
:mobile_qr_login_is_enabled,
|
||||
:microsoft_sync_enabled, :microsoft_sync_tenant, :microsoft_sync_login_attribute,
|
||||
:open_registration, :outgoing_email_default_name, :prevent_course_availability_editing_by_teachers,
|
||||
:prevent_course_renaming_by_teachers, :restrict_quiz_questions,
|
||||
{:restrict_student_future_listing => [:value, :locked]}.freeze,
|
||||
{:restrict_student_future_view => [:value, :locked]}.freeze,
|
||||
{:restrict_student_past_view => [:value, :locked]}.freeze,
|
||||
:self_enrollment, :show_scheduler, :sis_app_token, :sis_app_url,
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
# frozen_string_literal: true
|
||||
#
|
||||
# Copyright (C) 2021 - present 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 MicrosoftSync::Concerns
|
||||
module Settings
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
VALID_SYNC_LOGIN_ATTRIBUTES = %w(sub email oid preferred_username).freeze
|
||||
|
||||
def set_microsoft_sync_settings(enabled, tenant, login_attribute)
|
||||
return if enabled.nil? && tenant.blank? && login_attribute.blank?
|
||||
return unless valid_settings?(enabled, tenant, login_attribute)
|
||||
|
||||
@account.settings[:microsoft_sync_enabled] = format_enabled(enabled)
|
||||
@account.settings[:microsoft_sync_tenant] = format_tenant(tenant)
|
||||
@account.settings[:microsoft_sync_login_attribute] = format_login_attribute(login_attribute)
|
||||
end
|
||||
|
||||
def format_enabled(sync_enabled)
|
||||
ActiveModel::Type::Boolean.new.cast(sync_enabled)
|
||||
end
|
||||
|
||||
# A valid tenant is effectively a domain name (ex: canvastest2.onmicrosoft.com), consisting
|
||||
# of alphanumeric characters, with the restriction that each subdomain cannot start or end with
|
||||
# a hyphen. Normally we would use something like URI.parse(tenant), but that allows domains
|
||||
# that aren't valid, so we had to make a custom regex.
|
||||
def format_tenant(tenant)
|
||||
return nil if tenant.blank?
|
||||
|
||||
tenant = tenant.strip
|
||||
# Uses look ahead and look behind to ensure we don't start/end with hyphens in any subdomains/TLD
|
||||
regex = /^((?!-)[A-Za-z0-9-]+(?<!-)\.)+(?!-)[A-Za-z0-9-]+(?<!-)$/
|
||||
return tenant if tenant =~ regex
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
def format_login_attribute(login_attr)
|
||||
return nil unless VALID_SYNC_LOGIN_ATTRIBUTES.include?(login_attr)
|
||||
|
||||
login_attr
|
||||
end
|
||||
|
||||
# Checks if the passed settings are valid, and adds error messages as appropriate
|
||||
def valid_settings?(enabled, tenant, attribute)
|
||||
unless @account.root_account.feature_enabled?(:microsoft_group_enrollments_syncing)
|
||||
@account.errors.add(:bad_request,
|
||||
t("This account doesn't allow for Microsoft Teams sync to be enabled. Please enable the \"Microsoft Group enrollment syncing\" feature flag before editing any settings"))
|
||||
return false
|
||||
end
|
||||
enabled = format_enabled(enabled)
|
||||
if enabled.nil?
|
||||
@account.errors.add(:bad_request, t("You must specify whether to enable or disable Microsoft Teams sync"))
|
||||
false
|
||||
elsif enabled && (tenant.blank? || attribute.blank?)
|
||||
@account.errors.add(:bad_request, t("You must provide a tenant and login attribute to enabled Microsoft Teams sync"))
|
||||
false
|
||||
elsif enabled && format_tenant(tenant).blank?
|
||||
@account.errors.add(:bad_request, t("Invalid Microsoft Sync tenant given. Please validate your tenant"))
|
||||
false
|
||||
elsif enabled && format_login_attribute(attribute).blank?
|
||||
@account.errors.add(:bad_request,
|
||||
t("Invalid Microsoft Teams Sync login attribute. Valid login attributes: %{valid_attributes}",
|
||||
valid_attributes: VALID_SYNC_LOGIN_ATTRIBUTES.to_sentence(:or)))
|
||||
false
|
||||
end
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
|
@ -245,6 +245,11 @@ class Account < ActiveRecord::Base
|
|||
add_setting :global_includes, :root_only => true, :boolean => true, :default => false
|
||||
add_setting :sub_account_includes, :boolean => true, :default => false
|
||||
|
||||
# Microsoft Sync Account Settings
|
||||
add_setting :microsoft_sync_enabled, :root_only => true, :boolean => true, :default => false
|
||||
add_setting :microsoft_sync_tenant, :root_only => true
|
||||
add_setting :microsoft_sync_login_attribute, :root_only => true
|
||||
|
||||
# Help link settings
|
||||
add_setting :custom_help_links, :root_only => true
|
||||
add_setting :help_link_icon, :root_only => true
|
||||
|
|
|
@ -1496,6 +1496,7 @@ CanvasRails::Application.routes.draw do
|
|||
get 'accounts/:account_id/sub_accounts', action: :sub_accounts, as: 'sub_accounts'
|
||||
get 'accounts/:account_id/courses/:id', controller: :courses, action: :show, as: 'account_course_show'
|
||||
get 'accounts/:account_id/permissions', action: :permissions
|
||||
get 'accounts/:account_id/settings', action: :show_settings
|
||||
delete 'accounts/:account_id/users/:user_id', action: :remove_user
|
||||
put 'accounts/:account_id/users/:user_id/restore', action: :restore_user
|
||||
end
|
||||
|
|
|
@ -517,6 +517,188 @@ describe "Accounts API", type: :request do
|
|||
expect(@a1.settings).to be_empty
|
||||
end
|
||||
|
||||
context 'Microsoft Teams Sync' do
|
||||
|
||||
let(:update_sync_settings_params) do
|
||||
{
|
||||
account: {
|
||||
settings: {
|
||||
microsoft_sync_enabled: sync_enabled,
|
||||
microsoft_sync_tenant: tenant_name,
|
||||
microsoft_sync_login_attribute: attribute
|
||||
}.compact
|
||||
}
|
||||
}
|
||||
end
|
||||
let(:update_path) { "/api/v1/accounts/#{@a1.id}" }
|
||||
let(:sync_enabled) { true }
|
||||
let(:tenant_name) { "canvastest2.onmicrosoft.com" }
|
||||
let(:attribute) { "sub" }
|
||||
|
||||
before(:each) do
|
||||
user_session(@user)
|
||||
end
|
||||
|
||||
context 'microsoft_group_enrollments_syncing flag disabled' do
|
||||
before(:each) { @a1.disable_feature!(:microsoft_group_enrollments_syncing)}
|
||||
|
||||
it "shouldn't allow editing settings" do
|
||||
api_call(:put, update_path, header_options_hash,
|
||||
update_sync_settings_params, { expected_result: 400 })
|
||||
@a1.reload
|
||||
expect(@a1.settings.size).to be 0
|
||||
end
|
||||
|
||||
context 'subaccounts' do
|
||||
|
||||
let(:update_path) { "/api/v1/accounts/#{@a2.id}" }
|
||||
let(:header_options_hash) do
|
||||
{
|
||||
controller: 'accounts',
|
||||
action: 'update',
|
||||
id: @a2.to_param,
|
||||
format: 'json'
|
||||
}
|
||||
end
|
||||
|
||||
it "shouldn't allow subaccounts to edit settings" do
|
||||
api_call(:put, update_path, header_options_hash,
|
||||
update_sync_settings_params, { expected_result: 400 })
|
||||
@a2.reload
|
||||
expect(@a2.settings.size).to be 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'microsoft_group_enrollments_syncing flag enabled' do
|
||||
|
||||
before(:each) { @a1.enable_feature!(:microsoft_group_enrollments_syncing) }
|
||||
|
||||
context 'no tenant or login attribute provided' do
|
||||
|
||||
let(:tenant_name) { nil }
|
||||
let(:attribute) { nil }
|
||||
|
||||
it "shouldn't allow enabling sync" do
|
||||
api_call(:put, update_path, header_options_hash,
|
||||
update_sync_settings_params, { expected_result: 400 })
|
||||
@a1.reload
|
||||
expect(@a1.settings.size).to be 0
|
||||
end
|
||||
end
|
||||
|
||||
context 'invalid tenant name supplied' do
|
||||
|
||||
let(:tenant_name) { '^&abcd.com' }
|
||||
|
||||
it "shouldn't allow enabling sync" do
|
||||
api_call(:put, update_path, header_options_hash,
|
||||
update_sync_settings_params, { expected_result: 400 })
|
||||
@a1.reload
|
||||
expect(@a1.settings.size).to be 0
|
||||
end
|
||||
end
|
||||
|
||||
context 'invalid login attribute' do
|
||||
|
||||
let(:attribute) { 'garbage' }
|
||||
|
||||
it "shouldn't allow invalid login attributes" do
|
||||
api_call(:put, update_path, header_options_hash,
|
||||
update_sync_settings_params, { expected_result: 400 })
|
||||
@a1.reload
|
||||
expect(@a1.settings.size).to be 0
|
||||
end
|
||||
end
|
||||
|
||||
context 'non-admin user' do
|
||||
|
||||
let(:generic_user) { user_factory }
|
||||
|
||||
it "can't update settings" do
|
||||
api_call_as_user(generic_user, :put, update_path, header_options_hash,
|
||||
update_sync_settings_params, { expected_result: 401 })
|
||||
@a1.reload
|
||||
expect(@a1.settings.size).to eq 0
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'disabling sync' do
|
||||
|
||||
context('no tenant or login attribute specified') do
|
||||
|
||||
let(:sync_enabled) { false }
|
||||
let(:tenant_name) { nil }
|
||||
let(:attribute) { nil }
|
||||
|
||||
it "allows updating settings" do
|
||||
api_call(:put, update_path, header_options_hash,
|
||||
update_sync_settings_params, { expected_result: 200 })
|
||||
@a1.reload
|
||||
expect(@a1.settings).to eq update_sync_settings_params[:account][:settings]
|
||||
end
|
||||
end
|
||||
|
||||
it "allows specifying a tenant or login attribute" do
|
||||
api_call(:put, update_path, header_options_hash,
|
||||
update_sync_settings_params, { expected_result: 200 })
|
||||
@a1.reload
|
||||
expect(@a1.settings).to eq update_sync_settings_params[:account][:settings]
|
||||
end
|
||||
end
|
||||
|
||||
context 'admin user' do
|
||||
it "should save valid settings" do
|
||||
api_call(:put, update_path, header_options_hash,
|
||||
update_sync_settings_params, { expected_result: 200 })
|
||||
@a1.reload
|
||||
expect(@a1.settings).to eq update_sync_settings_params[:account][:settings]
|
||||
end
|
||||
|
||||
it 'should allow strings to be used for sync_enabled' do
|
||||
update_sync_settings_params[:account][:settings][:microsoft_sync_enabled] = "true"
|
||||
api_call(:put, update_path, header_options_hash,
|
||||
update_sync_settings_params, { expected_result: 200 })
|
||||
@a1.reload
|
||||
expect(@a1.settings[:microsoft_sync_enabled]).to be true
|
||||
expect(@a1.settings[:microsoft_sync_tenant]).to eq tenant_name
|
||||
expect(@a1.settings[:microsoft_sync_login_attribute]).to eq attribute
|
||||
end
|
||||
|
||||
it "should allow setting the login attribute to any of the allowed attributes" do
|
||||
%w(sub email oid preferred_username).each do |attribute|
|
||||
update_sync_settings_params[:account][:settings][:microsoft_sync_login_attribute] = attribute
|
||||
api_call(:put, update_path, header_options_hash,
|
||||
update_sync_settings_params, { expected_result: 200 })
|
||||
@a1.reload
|
||||
expect(@a1.settings).to eq update_sync_settings_params[:account][:settings]
|
||||
end
|
||||
end
|
||||
|
||||
context 'subaccounts' do
|
||||
|
||||
let(:update_path) { "/api/v1/accounts/#{@a2.id}" }
|
||||
let(:header_options_hash) do
|
||||
{
|
||||
controller: 'accounts',
|
||||
action: 'update',
|
||||
id: @a2.to_param,
|
||||
format: 'json'
|
||||
}
|
||||
end
|
||||
|
||||
it "should save valid settings" do
|
||||
api_call(:put, update_path, header_options_hash,
|
||||
update_sync_settings_params, { expected_result: 200 })
|
||||
@a2.reload
|
||||
expect(@a2.settings).to eq update_sync_settings_params[:account][:settings]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with :manage_storage_quotas' do
|
||||
before(:once) do
|
||||
# remove the user from being an Admin
|
||||
|
@ -649,7 +831,7 @@ describe "Accounts API", type: :request do
|
|||
{ controller: 'accounts', action: 'update', id: @a2.to_param, format: 'json' },
|
||||
{ account: { course_template_id: template.id }})
|
||||
@a2.reload
|
||||
expect(@a2.course_template).to eq template
|
||||
expect(@a2.course_template).to eq template
|
||||
end
|
||||
|
||||
it "returns unauthorized when you don't have permission to change it" do
|
||||
|
@ -1398,6 +1580,24 @@ describe "Accounts API", type: :request do
|
|||
end
|
||||
end
|
||||
|
||||
context "show settings" do
|
||||
|
||||
let(:show_settings_path) { "/api/v1/accounts/#{@a1.id}/settings"}
|
||||
let(:show_settings_header) { { controller: :accounts, action: :show_settings, account_id: @a1.to_param, format: :json} }
|
||||
let(:generic_user) { user_factory }
|
||||
|
||||
it "shouldn't allow regular users to see settings" do
|
||||
api_call_as_user(generic_user, :get, show_settings_path, show_settings_header, {}, { expected_status: 401 })
|
||||
end
|
||||
|
||||
it "should allow account admins to see settings" do
|
||||
@a1.settings = { :microsoft_sync_enabled => true, :microsoft_sync_tenant => "testtenant.com" }
|
||||
@a1.save!
|
||||
json = api_call(:get, show_settings_path, show_settings_header, {}, { expected_status: 200 })
|
||||
expect(json).to eq(@a1.settings.with_indifferent_access)
|
||||
end
|
||||
end
|
||||
|
||||
context "account api extension" do
|
||||
module MockPlugin
|
||||
def self.extend_account_json(hash, account, user, session, includes)
|
||||
|
|
Loading…
Reference in New Issue