2011-02-01 09:57:29 +08:00
|
|
|
#
|
2014-11-04 04:38:34 +08:00
|
|
|
# Copyright (C) 2011 - 2014 Instructure, Inc.
|
2011-02-01 09:57:29 +08:00
|
|
|
#
|
|
|
|
# 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/>.
|
|
|
|
#
|
|
|
|
|
2015-04-09 01:21:08 +08:00
|
|
|
require 'atom'
|
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
class Account < ActiveRecord::Base
|
|
|
|
include Context
|
2013-07-03 02:37:29 +08:00
|
|
|
attr_accessible :name, :turnitin_account_id, :turnitin_shared_secret,
|
|
|
|
:turnitin_host, :turnitin_comments, :turnitin_pledge,
|
2011-02-01 09:57:29 +08:00
|
|
|
:default_time_zone, :parent_account, :settings, :default_storage_quota,
|
2012-07-03 03:16:08 +08:00
|
|
|
:default_storage_quota_mb, :storage_quota, :ip_filters, :default_locale,
|
A new way of doing css/sass & New Canvas Theme Editor
what this does:
* Changes the way we generate css so we are able to generate custom
css for people that use the theme editor.
* Sets everything up so we can push all of our static assets
(js, fonts, css, images, etc) to s3 pre-deploy and serve them
from cloudfront. Yay! faster canvas for everyone!
* as part of that, this enables the rails asset pipeline just so we
can use it to put md5s in our urls. we don't use it for any of the
coffeescript/sass/sprockets transformer stuff.
* adds a new "Theme editor" functionality (only for people that have
have the use-new-styles feature flag turned on) where an admin for
an account can pick their own colors/images for all the users
at their account/school.
* when the user is done saving things in theme editor, it will,
in a delayed job, generate all the css with against the variables
that user specified and push it to s3 so it will be available to
anyone else that requests it. (the delayed job will shell
out to a node.js executable called `brandable_css`).
* ability to pick an existing shared theme and to reset to
blank theme. closes: CNVS-19685
* gets rid of jammit.
test plan:
(this is exaustive, so not every person has to do every step
but we should make sure at least someone does each of these things.
maybe as part of the review add a comment if you have done one of these
bulletpoints)
* before you check this out, compile all css and copy the
public/stylsheets_compiled directory somewhere. after you check out
this code and regenerate all the css. make sure there are no
significant changes to the css output. (we updated the versions of
node-sass and autoprefixer that we use so we want to make sure they
don't change things in a way we weren't expecting)
* make sure the way we load css for handlebars templates still works.
eg: if there is a handlebars template at
app/views/jst/some/template.handlebars
if there is also a scss file at
app/stylesheets/jst/some/template.scss
then that stylesheet should get loaded when that template is rendered
* check out the code and run migrations. browse around canvas,
make sure css and js files load correctly as before.
* cody, jacob, or someone on queso: look at the db migrations and
make sure everything looks good and that I am handling sharding
correctly.
* verify that both rake canvas:compile_assets and guard, works as well
as `node_modules/.bin/brandable_css` (note: if you have
"node_modules/.bin" in your PATH (which you should), it will also
work with just `brandable_css`)
* verify that passing the --watch option to
`.bin/node_modules/brandable_css` works and picks up changes to
sass files, images, fonts, or any other resource that goes into
a css file. and that it only recompiles the css files that actually
depend on that file.
* go to https://github.com/ryankshaw/brandable_css and check out the
code there. that is what is actually doing the sass compiling
* create a config/canvas_cdn.yml file and add aws access creds and
an s3 bucket and cdn hostname (for testing, you can use the credentials
for instructure_uploads_engineering from
https://gollum.instructure.com/OtherServiceTestAccounts ). for a test
cdn hostname you can use https://diu0rq5m1weh1.cloudfront.net. that
is a cloudfront bucket I set up on my personal account that points to
instructure_uploads_engineering
* run rake canvas:compile_assets again, this time, at the end, you
should see it run the assets:precompile task that puts md5s in filenames
and, gzipps them, and copys them to public/assets.
then you should see it run canvas:cdn:upload_to_s3
(look at log/development.log for progress),
which pushes everything to s3.
closes: CNVS-17333 CNVS-17430 CNVS-17337
* try out the theme editor: turn on new styles, go to accounts/x
(where x is the @domain root acount you are testing from) and click
the "theme editor" button on the right side of the page.
that should take you to a page that has the ability to pick colors/images
on the left side and preview your changes in an iframe on the right
closes: CNVS-19360 CNVS-20551
* test the "preview", "save", "reset", and "choose existing" functionality
closes: CNVS-17339 CNVS-17338 CNVS-19685
* make sure that the themeeditor works both if you have
config/canvas_cdn.yml set up and enabled as well as if you don't.
if it is enabled, you should see it push the css for just that new
brand config to s3 when you hit preview, and the css
should be accessible from the cdn you configured.
Change-Id: Ie0a812d04f5eeb40e7df7e71941ff63ea51a4d22
Reviewed-on: https://gerrit.instructure.com/53873
Tested-by: Jenkins
QA-Review: Jeremy Putnam <jeremyp@instructure.com>
Reviewed-by: Jacob Fugal <jacob@instructure.com>
Product-Review: Ryan Shaw <ryan@instructure.com>
2015-02-12 03:51:05 +08:00
|
|
|
:default_user_storage_quota_mb, :default_group_storage_quota_mb, :integration_id, :brand_config_md5
|
2011-02-01 09:57:29 +08:00
|
|
|
|
2014-04-11 03:03:43 +08:00
|
|
|
EXPORTABLE_ATTRIBUTES = [:id, :name, :created_at, :updated_at, :workflow_state, :deleted_at,
|
2014-09-08 20:48:45 +08:00
|
|
|
:default_time_zone, :external_status, :storage_quota,
|
2014-04-11 03:03:43 +08:00
|
|
|
:enable_user_notes, :allowed_services, :turnitin_pledge, :turnitin_comments,
|
|
|
|
:turnitin_account_id, :allow_sis_import, :sis_source_id, :equella_endpoint,
|
|
|
|
:settings, :uuid, :default_locale, :default_user_storage_quota, :turnitin_host,
|
2014-05-22 12:55:43 +08:00
|
|
|
:created_by_id, :lti_guid, :default_group_storage_quota, :lti_context_id
|
2014-04-11 03:03:43 +08:00
|
|
|
]
|
|
|
|
|
|
|
|
EXPORTABLE_ASSOCIATIONS = [
|
|
|
|
:courses, :group_categories, :groups, :enrollment_terms, :enrollments, :account_users, :course_sections,
|
2014-06-13 03:06:51 +08:00
|
|
|
:pseudonyms, :attachments, :folders, :active_assignments, :grading_standards, :assessment_question_banks,
|
|
|
|
:roles, :announcements, :alerts, :course_account_associations, :user_account_associations
|
2014-04-11 03:03:43 +08:00
|
|
|
]
|
|
|
|
|
2014-05-20 05:37:01 +08:00
|
|
|
INSTANCE_GUID_SUFFIX = 'canvas-lms'
|
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
include Workflow
|
|
|
|
belongs_to :parent_account, :class_name => 'Account'
|
|
|
|
belongs_to :root_account, :class_name => 'Account'
|
|
|
|
authenticates_many :pseudonym_sessions
|
2014-04-11 03:03:43 +08:00
|
|
|
has_many :courses
|
2011-02-01 09:57:29 +08:00
|
|
|
has_many :all_courses, :class_name => 'Course', :foreign_key => 'root_account_id'
|
2011-09-22 04:31:36 +08:00
|
|
|
has_many :group_categories, :as => :context, :conditions => ['deleted_at IS NULL']
|
|
|
|
has_many :all_group_categories, :class_name => 'GroupCategory', :as => :context
|
2014-04-11 03:03:43 +08:00
|
|
|
has_many :groups, :as => :context
|
2012-09-20 12:39:49 +08:00
|
|
|
has_many :all_groups, :class_name => 'Group', :foreign_key => 'root_account_id'
|
2014-04-11 03:03:43 +08:00
|
|
|
has_many :enrollment_terms, :foreign_key => 'root_account_id'
|
|
|
|
has_many :enrollments, :foreign_key => 'root_account_id', :conditions => ["enrollments.type != 'StudentViewEnrollment'"]
|
2015-01-13 07:42:02 +08:00
|
|
|
has_many :all_enrollments, :class_name => 'Enrollment', :foreign_key => 'root_account_id'
|
2011-02-01 09:57:29 +08:00
|
|
|
has_many :sub_accounts, :class_name => 'Account', :foreign_key => 'parent_account_id', :conditions => ['workflow_state != ?', 'deleted']
|
2011-08-05 23:38:31 +08:00
|
|
|
has_many :all_accounts, :class_name => 'Account', :foreign_key => 'root_account_id', :order => 'name'
|
2014-04-11 03:03:43 +08:00
|
|
|
has_many :account_users, :dependent => :destroy
|
|
|
|
has_many :course_sections, :foreign_key => 'root_account_id'
|
|
|
|
has_many :sis_batches
|
2011-06-08 00:31:14 +08:00
|
|
|
has_many :abstract_courses, :class_name => 'AbstractCourse', :foreign_key => 'account_id'
|
|
|
|
has_many :root_abstract_courses, :class_name => 'AbstractCourse', :foreign_key => 'root_account_id'
|
2014-04-11 03:03:43 +08:00
|
|
|
has_many :users, :through => :account_users
|
|
|
|
has_many :pseudonyms, :include => :user
|
2011-02-01 09:57:29 +08:00
|
|
|
has_many :role_overrides, :as => :context
|
|
|
|
has_many :course_account_associations
|
|
|
|
has_many :child_courses, :through => :course_account_associations, :source => :course, :conditions => ['course_account_associations.depth = 0']
|
2014-04-11 03:03:43 +08:00
|
|
|
has_many :attachments, :as => :context, :dependent => :destroy
|
|
|
|
has_many :active_assignments, :as => :context, :class_name => 'Assignment', :conditions => ['assignments.workflow_state != ?', 'deleted']
|
|
|
|
has_many :folders, :as => :context, :dependent => :destroy, :order => 'folders.name'
|
2011-02-01 09:57:29 +08:00
|
|
|
has_many :active_folders, :class_name => 'Folder', :as => :context, :conditions => ['folders.workflow_state != ?', 'deleted'], :order => 'folders.name'
|
2011-03-02 03:17:42 +08:00
|
|
|
has_many :active_folders_with_sub_folders, :class_name => 'Folder', :as => :context, :include => [:active_sub_folders], :conditions => ['folders.workflow_state != ?', 'deleted'], :order => 'folders.name'
|
2011-02-01 09:57:29 +08:00
|
|
|
has_many :active_folders_detailed, :class_name => 'Folder', :as => :context, :include => [:active_sub_folders, :active_file_attachments], :conditions => ['folders.workflow_state != ?', 'deleted'], :order => 'folders.name'
|
2015-06-05 00:42:53 +08:00
|
|
|
|
|
|
|
has_many :authentication_providers,
|
|
|
|
order: "position",
|
|
|
|
extend: AccountAuthorizationConfig::FindWithType,
|
|
|
|
class_name: "AccountAuthorizationConfig"
|
|
|
|
|
|
|
|
# Shim until plugins can be updated to use "authentication_providers"
|
|
|
|
has_many :account_authorization_configs,
|
|
|
|
order: "position",
|
|
|
|
extend: AccountAuthorizationConfig::FindWithType
|
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
has_many :account_reports
|
2014-04-11 03:03:43 +08:00
|
|
|
has_many :grading_standards, :as => :context, :conditions => ['workflow_state != ?', 'deleted']
|
|
|
|
has_many :assessment_questions, :through => :assessment_question_banks
|
|
|
|
has_many :assessment_question_banks, :as => :context, :include => [:assessment_questions, :assessment_question_bank_users]
|
2012-11-21 02:44:14 +08:00
|
|
|
has_many :roles
|
2012-11-28 04:52:33 +08:00
|
|
|
has_many :all_roles, :class_name => 'Role', :foreign_key => 'root_account_id'
|
2013-01-16 07:37:54 +08:00
|
|
|
has_many :progresses, :as => :context
|
2014-05-05 20:23:55 +08:00
|
|
|
has_many :content_migrations, :as => :context
|
add grading period group model
add GradingPeriodGroup, and change associations between GradingPeriods,
GradingPeriodGroups, Courses, and Accounts. also adjust the grading
periods controller to account for addition of grading period groups
closes CNVS-16538
test plan:
-run bundle exec rake db:migrate, and bundle exec rake db:migrate RAILS_ENV=test
-verify the migrations successfully run
-open the rails console in sandbox: bundle exec rails c -s
-create a course, a few grading periods, and a grading period group. Add the grading periods to the group. Assign
the grading period group to the course.
$ course = Course.create
$ grading_period1 = GradingPeriod.create(weight: 25.0, start_date: Time.zone.now, end_date: 2.days.from_now)
$ grading_period2 = GradingPeriod.create(weight: 30.0, start_date: Time.zone.now, end_date: 2.days.from_now)
$ grading_period_group = GradingPeriodGroup.create()
$ grading_period_group.grading_periods << grading_period1
$ grading_period_group.grading_periods << grading_period2
$ grading_period_group.course = course
-verify the associations are working as expected, i.e. a GradingPeriodGroup has GradingPeriods, a GradingPeriod
belongs to a GradingPeriodGroup, and a GradingPeriodGroup belongs to a course or account.
$ grading_period_group.grading_periods #should return an array containing grading_period1 and grading_period2
$ grading_period1.grading_period_group #should return grading_period_group
$ grading_period2.grading_period_group #should return grading_period_group
$ grading_period_group.course #should return course
$ grading_period_group.account #should return nil (should not throw error)
Change-Id: I9d7465431dabd2afa18e7a8a33706b9a78a94cd1
Reviewed-on: https://gerrit.instructure.com/43512
Tested-by: Jenkins <jenkins@instructure.com>
Reviewed-by: Josh Simpson <jsimpson@instructure.com>
QA-Review: Amber Taniuchi <amber@instructure.com>
Reviewed-by: Jacob Fugal <jacob@instructure.com>
Product-Review: Spencer Olson <solson@instructure.com>
2014-10-30 02:42:41 +08:00
|
|
|
has_many :grading_period_groups, dependent: :destroy
|
|
|
|
has_many :grading_periods, through: :grading_period_groups
|
2014-05-05 20:23:55 +08:00
|
|
|
|
2011-08-27 08:33:01 +08:00
|
|
|
def inherited_assessment_question_banks(include_self = false, *additional_contexts)
|
|
|
|
sql = []
|
|
|
|
conds = []
|
|
|
|
contexts = additional_contexts + account_chain
|
|
|
|
contexts.delete(self) unless include_self
|
|
|
|
contexts.each { |c|
|
|
|
|
sql << "context_type = ? AND context_id = ?"
|
|
|
|
conds += [c.class.to_s, c.id]
|
|
|
|
}
|
|
|
|
conds.unshift(sql.join(" OR "))
|
2013-03-19 03:07:47 +08:00
|
|
|
AssessmentQuestionBank.where(conds)
|
2011-08-27 08:33:01 +08:00
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
learning outcomes refactor
This list is *NOT* complete, some items may have snuck in that I forgot
to note, and/or some of the noted items may not be completely functional
yet.
Specs need to be written around a lot of this, other specs will no doubt
need to be fixed.
Some things, particularly around LearningOutcomeGroups will need data
migrations that aren't there yet.
* remove LearningOutcome.non_rubric_outcomes? and replace with false
where invoked
* remove LearningOutcome.enabled? and replace with true where invoked
* remove never-taken branches
* remove the shared/aligned_outcomes partial and it's supporting
javascript, since it's now empty
* remove js handler for add_outcome_alignment_link and supporting
method since it only occurred in never-taken branches
* mix LearningOutcomeContext into Course and Account
* replace LearningOutcomeGroup.default_for(context) with
LearningOutcomeContext#root_outcome_group
* rename LearningOutcome#content_tags to LearningOutcome#alignments
* rename LearningOutcomeGroup#content_tags to
LearningOutcomeGroup#child_links, and properly restrict
* remove ContentTag[Alignment]#rubric_association_id, add
ContentTag[Alignment]#has_rubric_association? that looks at the
presence of the content's rubric_association_id
* condition off the assignment having a rubric_association rather than
filtering tags by has_rubric_association (which just looks back at
the assignment). all or none of the assignment's alignments are
forced to have the association (via the assignment). this was true in
practice before, is now codified (and more efficient)
* rename AssessmentQuestionBank#learning_outcome_tags to
AssessmentQuestionBank#learning_outcome_alignments
* rename Assignment#learning_outcome_tags to
Assignment#learning_outcome_alignments
* rename Rubric#learning_outcome_tags to
Rubric#learning_outcome_alignments
* move/rename (Course|Account)#learning_outcome_tags to
LearningOutcomeContext#learning_outcome_links
* move/rename Account#learning_outcomes (corrected) and
Course#learning_outcomes to
LearningOutcomeContext#linked_learning_outcomes
* move/rename Account#created_learning_outcomes and
Course#created_learning_outcomes to
LearningOutcomeContext#created_learning_outcomes
* clarify and correct usage of linked_learning_outcomes vs.
created_learning_outcomes
* move/rename (Account|Account)#learning_outcome_groups to
LearningOutcomeContext#learning_outcome_groups
* remove unused Account#associated_learning_outcomes
* just remove one link to a learning outcome when deleting
* merge Account#has_outcomes?, Course#has_outcomes? and
Course#has_outcomes into LearningOutcomeContext#has_outcomes?, add a
use in Context#active_record_types
* kill LearningOutcomeGroup#root_learning_outcome_group (unused)
* rename LearningOutcomeResult#content_tag to
LearningOutcomeResult#alignment
* kill unused (and broken) OutcomesController#add_outcome_group
* kill unused OutcomesController#update_outcomes_for_asset
* kill unused OutcomesController#outcomes_for_asset
* remove unused (outside specs, correct specs)
AssessmentQuestionBank#outcomes=
* remove unused ContentTag#learning_outcome_content
* replace ContentTag.learning_outcome_tags_for(asset) (only ever called
with asset=an assignment) with call to
Assignment#learning_outcome_alignments
* remove unused ContentTag.not_rubric
* remove (now) unused ContentTag.include_outcome
* remove unused LearningOutcome#learning_outcome_group_associations
* avoid explicit use of ContentTag in outcome-related specs
* replace LearningOutcomeGroup#learning_outcome_tags with
LearningOutcomeGroup#child_outcome_links (and only use for outcome
links; not tags for child groups)
* split ContentTag#create_outcome_result into
Submission#create_outcome_result,
QuizSubmission#create_outcome_result, and
RubricAssessment#create_outcome_result. fix some bugs along the way
* refactor ContentTag.outcome_tags_for_banks and some code from
QuizSubmission#(track_outcomes|update_outcomes_for_assessment_questions)
into QuizSubmission#questions_and_alignments
* refactor RubricAssociation#update_outcome_relations and
Rubric#update_alignments into LearningOutcome.update_alignments
* don't use ContentTag#rubric_association with outcome alignments; use
the tag's content's rubric_association in its place (they should have
been equal anyways)
* refactor LearningOutcome.available_in_context and
@context.root_outcome_group.sorted_all_outcomes (only time
sorted_all_outcomes is used) into
LearningOutcomeContext#available_outcomes and
LearningOutcomeContext#available_outcome
* overhaul LearningOutcomeGroup#sorted_content and rename to
LearningOutcomeGroup#sorted_children. it not returns ContentTags
(outcome links) and LearningOutcomeGroups, vs. LearningOutcomes and
LearningOutcomeGroups; fix usages appropriately
* fix UI for arranging/deleting outcome links and groups within a group
to refer to the outcome link rather than the outcome
Change-Id: I85d99f2634f7206332cb1f5d5ea575b428988d4b
Reviewed-on: https://gerrit.instructure.com/12590
Reviewed-by: Jacob Fugal <jacob@instructure.com>
Tested-by: Jacob Fugal <jacob@instructure.com>
2012-07-13 01:16:13 +08:00
|
|
|
include LearningOutcomeContext
|
2013-06-18 05:12:49 +08:00
|
|
|
include RubricContext
|
learning outcomes refactor
This list is *NOT* complete, some items may have snuck in that I forgot
to note, and/or some of the noted items may not be completely functional
yet.
Specs need to be written around a lot of this, other specs will no doubt
need to be fixed.
Some things, particularly around LearningOutcomeGroups will need data
migrations that aren't there yet.
* remove LearningOutcome.non_rubric_outcomes? and replace with false
where invoked
* remove LearningOutcome.enabled? and replace with true where invoked
* remove never-taken branches
* remove the shared/aligned_outcomes partial and it's supporting
javascript, since it's now empty
* remove js handler for add_outcome_alignment_link and supporting
method since it only occurred in never-taken branches
* mix LearningOutcomeContext into Course and Account
* replace LearningOutcomeGroup.default_for(context) with
LearningOutcomeContext#root_outcome_group
* rename LearningOutcome#content_tags to LearningOutcome#alignments
* rename LearningOutcomeGroup#content_tags to
LearningOutcomeGroup#child_links, and properly restrict
* remove ContentTag[Alignment]#rubric_association_id, add
ContentTag[Alignment]#has_rubric_association? that looks at the
presence of the content's rubric_association_id
* condition off the assignment having a rubric_association rather than
filtering tags by has_rubric_association (which just looks back at
the assignment). all or none of the assignment's alignments are
forced to have the association (via the assignment). this was true in
practice before, is now codified (and more efficient)
* rename AssessmentQuestionBank#learning_outcome_tags to
AssessmentQuestionBank#learning_outcome_alignments
* rename Assignment#learning_outcome_tags to
Assignment#learning_outcome_alignments
* rename Rubric#learning_outcome_tags to
Rubric#learning_outcome_alignments
* move/rename (Course|Account)#learning_outcome_tags to
LearningOutcomeContext#learning_outcome_links
* move/rename Account#learning_outcomes (corrected) and
Course#learning_outcomes to
LearningOutcomeContext#linked_learning_outcomes
* move/rename Account#created_learning_outcomes and
Course#created_learning_outcomes to
LearningOutcomeContext#created_learning_outcomes
* clarify and correct usage of linked_learning_outcomes vs.
created_learning_outcomes
* move/rename (Account|Account)#learning_outcome_groups to
LearningOutcomeContext#learning_outcome_groups
* remove unused Account#associated_learning_outcomes
* just remove one link to a learning outcome when deleting
* merge Account#has_outcomes?, Course#has_outcomes? and
Course#has_outcomes into LearningOutcomeContext#has_outcomes?, add a
use in Context#active_record_types
* kill LearningOutcomeGroup#root_learning_outcome_group (unused)
* rename LearningOutcomeResult#content_tag to
LearningOutcomeResult#alignment
* kill unused (and broken) OutcomesController#add_outcome_group
* kill unused OutcomesController#update_outcomes_for_asset
* kill unused OutcomesController#outcomes_for_asset
* remove unused (outside specs, correct specs)
AssessmentQuestionBank#outcomes=
* remove unused ContentTag#learning_outcome_content
* replace ContentTag.learning_outcome_tags_for(asset) (only ever called
with asset=an assignment) with call to
Assignment#learning_outcome_alignments
* remove unused ContentTag.not_rubric
* remove (now) unused ContentTag.include_outcome
* remove unused LearningOutcome#learning_outcome_group_associations
* avoid explicit use of ContentTag in outcome-related specs
* replace LearningOutcomeGroup#learning_outcome_tags with
LearningOutcomeGroup#child_outcome_links (and only use for outcome
links; not tags for child groups)
* split ContentTag#create_outcome_result into
Submission#create_outcome_result,
QuizSubmission#create_outcome_result, and
RubricAssessment#create_outcome_result. fix some bugs along the way
* refactor ContentTag.outcome_tags_for_banks and some code from
QuizSubmission#(track_outcomes|update_outcomes_for_assessment_questions)
into QuizSubmission#questions_and_alignments
* refactor RubricAssociation#update_outcome_relations and
Rubric#update_alignments into LearningOutcome.update_alignments
* don't use ContentTag#rubric_association with outcome alignments; use
the tag's content's rubric_association in its place (they should have
been equal anyways)
* refactor LearningOutcome.available_in_context and
@context.root_outcome_group.sorted_all_outcomes (only time
sorted_all_outcomes is used) into
LearningOutcomeContext#available_outcomes and
LearningOutcomeContext#available_outcome
* overhaul LearningOutcomeGroup#sorted_content and rename to
LearningOutcomeGroup#sorted_children. it not returns ContentTags
(outcome links) and LearningOutcomeGroups, vs. LearningOutcomes and
LearningOutcomeGroups; fix usages appropriately
* fix UI for arranging/deleting outcome links and groups within a group
to refer to the outcome link rather than the outcome
Change-Id: I85d99f2634f7206332cb1f5d5ea575b428988d4b
Reviewed-on: https://gerrit.instructure.com/12590
Reviewed-by: Jacob Fugal <jacob@instructure.com>
Tested-by: Jacob Fugal <jacob@instructure.com>
2012-07-13 01:16:13 +08:00
|
|
|
|
2011-03-10 00:11:22 +08:00
|
|
|
has_many :context_external_tools, :as => :context, :dependent => :destroy, :order => 'name'
|
2011-02-01 09:57:29 +08:00
|
|
|
has_many :error_reports
|
2014-04-11 03:03:43 +08:00
|
|
|
has_many :announcements, :class_name => 'AccountNotification'
|
|
|
|
has_many :alerts, :as => :context, :include => :criteria
|
2011-08-30 02:25:20 +08:00
|
|
|
has_many :user_account_associations
|
2012-03-23 04:28:38 +08:00
|
|
|
has_many :report_snapshots
|
2014-10-31 03:15:58 +08:00
|
|
|
has_many :external_integration_keys, :as => :context, :dependent => :destroy
|
A new way of doing css/sass & New Canvas Theme Editor
what this does:
* Changes the way we generate css so we are able to generate custom
css for people that use the theme editor.
* Sets everything up so we can push all of our static assets
(js, fonts, css, images, etc) to s3 pre-deploy and serve them
from cloudfront. Yay! faster canvas for everyone!
* as part of that, this enables the rails asset pipeline just so we
can use it to put md5s in our urls. we don't use it for any of the
coffeescript/sass/sprockets transformer stuff.
* adds a new "Theme editor" functionality (only for people that have
have the use-new-styles feature flag turned on) where an admin for
an account can pick their own colors/images for all the users
at their account/school.
* when the user is done saving things in theme editor, it will,
in a delayed job, generate all the css with against the variables
that user specified and push it to s3 so it will be available to
anyone else that requests it. (the delayed job will shell
out to a node.js executable called `brandable_css`).
* ability to pick an existing shared theme and to reset to
blank theme. closes: CNVS-19685
* gets rid of jammit.
test plan:
(this is exaustive, so not every person has to do every step
but we should make sure at least someone does each of these things.
maybe as part of the review add a comment if you have done one of these
bulletpoints)
* before you check this out, compile all css and copy the
public/stylsheets_compiled directory somewhere. after you check out
this code and regenerate all the css. make sure there are no
significant changes to the css output. (we updated the versions of
node-sass and autoprefixer that we use so we want to make sure they
don't change things in a way we weren't expecting)
* make sure the way we load css for handlebars templates still works.
eg: if there is a handlebars template at
app/views/jst/some/template.handlebars
if there is also a scss file at
app/stylesheets/jst/some/template.scss
then that stylesheet should get loaded when that template is rendered
* check out the code and run migrations. browse around canvas,
make sure css and js files load correctly as before.
* cody, jacob, or someone on queso: look at the db migrations and
make sure everything looks good and that I am handling sharding
correctly.
* verify that both rake canvas:compile_assets and guard, works as well
as `node_modules/.bin/brandable_css` (note: if you have
"node_modules/.bin" in your PATH (which you should), it will also
work with just `brandable_css`)
* verify that passing the --watch option to
`.bin/node_modules/brandable_css` works and picks up changes to
sass files, images, fonts, or any other resource that goes into
a css file. and that it only recompiles the css files that actually
depend on that file.
* go to https://github.com/ryankshaw/brandable_css and check out the
code there. that is what is actually doing the sass compiling
* create a config/canvas_cdn.yml file and add aws access creds and
an s3 bucket and cdn hostname (for testing, you can use the credentials
for instructure_uploads_engineering from
https://gollum.instructure.com/OtherServiceTestAccounts ). for a test
cdn hostname you can use https://diu0rq5m1weh1.cloudfront.net. that
is a cloudfront bucket I set up on my personal account that points to
instructure_uploads_engineering
* run rake canvas:compile_assets again, this time, at the end, you
should see it run the assets:precompile task that puts md5s in filenames
and, gzipps them, and copys them to public/assets.
then you should see it run canvas:cdn:upload_to_s3
(look at log/development.log for progress),
which pushes everything to s3.
closes: CNVS-17333 CNVS-17430 CNVS-17337
* try out the theme editor: turn on new styles, go to accounts/x
(where x is the @domain root acount you are testing from) and click
the "theme editor" button on the right side of the page.
that should take you to a page that has the ability to pick colors/images
on the left side and preview your changes in an iframe on the right
closes: CNVS-19360 CNVS-20551
* test the "preview", "save", "reset", and "choose existing" functionality
closes: CNVS-17339 CNVS-17338 CNVS-19685
* make sure that the themeeditor works both if you have
config/canvas_cdn.yml set up and enabled as well as if you don't.
if it is enabled, you should see it push the css for just that new
brand config to s3 when you hit preview, and the css
should be accessible from the cdn you configured.
Change-Id: Ie0a812d04f5eeb40e7df7e71941ff63ea51a4d22
Reviewed-on: https://gerrit.instructure.com/53873
Tested-by: Jenkins
QA-Review: Jeremy Putnam <jeremyp@instructure.com>
Reviewed-by: Jacob Fugal <jacob@instructure.com>
Product-Review: Ryan Shaw <ryan@instructure.com>
2015-02-12 03:51:05 +08:00
|
|
|
belongs_to :brand_config, foreign_key: "brand_config_md5"
|
2011-02-01 09:57:29 +08:00
|
|
|
|
2011-06-01 04:47:28 +08:00
|
|
|
before_validation :verify_unique_sis_source_id
|
2011-02-01 09:57:29 +08:00
|
|
|
before_save :ensure_defaults
|
|
|
|
after_save :update_account_associations_if_changed
|
2015-01-15 04:59:43 +08:00
|
|
|
|
2015-07-01 23:31:41 +08:00
|
|
|
before_save :setup_cache_invalidation
|
|
|
|
after_save :invalidate_caches_if_changed
|
2015-01-15 04:59:43 +08:00
|
|
|
|
2011-02-08 07:11:00 +08:00
|
|
|
after_create :default_enrollment_term
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
serialize :settings, Hash
|
2013-07-23 05:10:01 +08:00
|
|
|
include TimeZoneHelper
|
2014-04-03 01:59:09 +08:00
|
|
|
|
2013-07-23 05:10:01 +08:00
|
|
|
time_zone_attribute :default_time_zone, default: "America/Denver"
|
2014-04-03 01:59:09 +08:00
|
|
|
def default_time_zone_with_root_account
|
|
|
|
if read_attribute(:default_time_zone) || root_account?
|
|
|
|
default_time_zone_without_root_account
|
|
|
|
else
|
|
|
|
root_account.default_time_zone
|
|
|
|
end
|
|
|
|
end
|
|
|
|
alias_method_chain :default_time_zone, :root_account
|
2014-05-14 07:48:56 +08:00
|
|
|
alias_method :time_zone, :default_time_zone
|
2011-02-01 09:57:29 +08:00
|
|
|
|
2011-07-13 04:31:40 +08:00
|
|
|
validates_locale :default_locale, :allow_nil => true
|
2013-08-20 01:36:53 +08:00
|
|
|
validates_length_of :name, :maximum => maximum_string_length, :allow_blank => true
|
2012-08-07 00:20:48 +08:00
|
|
|
validate :account_chain_loop, :if => :parent_account_id_changed?
|
2012-10-11 02:58:09 +08:00
|
|
|
validate :validate_auth_discovery_url
|
2013-08-08 06:19:48 +08:00
|
|
|
validates_presence_of :workflow_state
|
2011-07-13 04:31:40 +08:00
|
|
|
|
2011-09-22 01:36:45 +08:00
|
|
|
include StickySisFields
|
|
|
|
are_sis_sticky :name
|
|
|
|
|
feature flags infrastructure and API
test plan:
- install the test_features plugin (since no real features exist yet)
- render and consult the feature flags documentation
- have a test environment with a root account,
sub-account, course in sub-account, and user
- Use the "list features" endpoint as a root account admin
(with no site admin privileges), on the root account context, and
confirm that hidden features do not show up
- Use the "list features" endpoint as a site admin user,
on the root account context, and confirm that hidden features
show up
- Use the "list features" endpoint on the site admin account
and confirm the hidden features show up
- Use the "set feature flag" endpoint on a hidden feature on site
admin and ensure the feature becomes visible in all root accounts
- Use the "set feature flag endpoint" on a hidden feature on a
single root account, and ensure the feature becomes visible to
that root account and not others
- Confirm that root_opt_in features appear "Off" by default
in root accounts, after being "Allowed" in code or site admin
- Confirm a feature flag that is set to "on" or "off" (vs. "allowed")
cannot be overridden in a lower context (and the API returns
locked=true for them)
- Confirm that setting locking_account_id requires admin rights
in the locking account
- Confirm that a feature flag with locking_account_id cannot be
changed without admin rights in the locking account (e.g.,
set a feature flag on a course, locked with the root account's id,
and make sure a teacher who is not an account admin can't change it)
- Confirm feature flags can be deleted with the "remove feature flag"
endpoint (and they are only deleted where they are defined, not
when called on an object that inherits a flag)
Change-Id: I3e12e23b4454889b6e8b263f1315e82d8f2ada52
Reviewed-on: https://gerrit.instructure.com/25502
Tested-by: Jenkins <jenkins@instructure.com>
QA-Review: Matt Fairbourn <mfairbourn@instructure.com>
Product-Review: Matt Goodwin <mattg@instructure.com>
Reviewed-by: Zach Pendleton <zachp@instructure.com>
2013-10-22 23:28:26 +08:00
|
|
|
include FeatureFlags
|
|
|
|
|
2011-07-13 04:31:40 +08:00
|
|
|
def default_locale(recurse = false)
|
2014-09-26 02:20:17 +08:00
|
|
|
result = read_attribute(:default_locale)
|
|
|
|
result ||= parent_account.default_locale(true) if recurse && parent_account
|
|
|
|
result = nil unless I18n.locale_available?(result)
|
|
|
|
result
|
2011-07-13 04:31:40 +08:00
|
|
|
end
|
|
|
|
|
2015-04-08 02:30:28 +08:00
|
|
|
include ::Account::Settings
|
2013-11-26 11:16:35 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
# these settings either are or could be easily added to
|
|
|
|
# the account settings page
|
2014-11-13 04:38:33 +08:00
|
|
|
add_setting :sis_app_token, :root_only => true
|
2011-11-22 03:28:20 +08:00
|
|
|
add_setting :global_includes, :root_only => true, :boolean => true, :default => false
|
sub-account branding; closes #9368
allow sub accounts to include their own global scripts and stylesheets. if
global includes are enabled on the root account, root account administrators
will have an option to enable them for immediate child accounts. those child
accounts can then choose to enable them for their sub-accounts, and so on down
the chain.
these includes are added to the page in order from highest to lowest account,
so sub-accounts are able to override styles added by their parents.
the logic for which styles to display on which pages is as follows:
- on account pages, include all styles in the chain from this account up to the
root account. this ensures that you can always see styles for account
X without any sub-account overrides on account X's page
- on course/group pages, include all styles in the chain from the account which
contains that course/group up to the root
- on the dashboard, calendar, user pages, and other pages that don't fall into
one of the above categories, we find the lowest account that contains all of
the current user's active classes + groups, and include styles from that
account up to the root
test plan:
- in a root account, create two sub-accounts, create courses in each of them,
and create 3 users, one enrolled only in the first course, one only in the
second course, and one enrolled in both courses.
- enable global includes on the root account (no sub-accounts yet) add files,
and make sure all three students see them.
- now enable sub-account includes, and add include files to each sub-account
- make sure both users in course 1 see include for sub-account 1
- make sure user 1 sees include for sub-account 1 on her dashboard, but user
3 does not.
Change-Id: I3d07d4bced39593f3084d5eac6ea3137666e319b
Reviewed-on: https://gerrit.instructure.com/12248
Tested-by: Jenkins <jenkins@instructure.com>
Reviewed-by: Simon Williams <simon@instructure.com>
2012-07-10 05:30:16 +08:00
|
|
|
add_setting :global_javascript, :condition => :allow_global_includes
|
|
|
|
add_setting :global_stylesheet, :condition => :allow_global_includes
|
|
|
|
add_setting :sub_account_includes, :condition => :allow_global_includes, :boolean => true, :default => false
|
2011-02-01 09:57:29 +08:00
|
|
|
add_setting :error_reporting, :hash => true, :values => [:action, :email, :url, :subject_param, :body_param], :root_only => true
|
2011-11-11 10:43:36 +08:00
|
|
|
add_setting :custom_help_links, :root_only => true
|
2011-02-01 09:57:29 +08:00
|
|
|
add_setting :prevent_course_renaming_by_teachers, :boolean => true, :root_only => true
|
refactor views for unified AAC administration
closes CNVS-20076
First, pull a presenter out of the AAC index
This thing needs more flexibility before introducing
multiple OAuth connectors. This commit adds a couple characterization
specs for the AAC controller, then drives out a presenter to pull
as much logic and config out of the nested views as possible.
Then, this commit refactors the
previously-somewhat-bespoke-and-presumptive sac
configuration into a workflow that shows each aac
in turn according to it's type, creating forms for
each type at the bottom, and
showing the relevant form for a new one based on
selection on the right.
Have regression tested in the browser to the level
of CRUD functionality, but also deserves solid QA
for SSO functionality post-configuration.
DONE:
-successful CAS creation/editing/deletion
-successful LDAP creation/editing/deletion
-proper differentiation between LDAP primary and secondary
-proper SAML creation/editing
-move away from "update_all" deprecated endpoint
-Selenium Spec fixes
-ensure discovery URL and debugging workflows for saml
-remove duplication from views
-tear down old JS workflow
-apply appropriate tests for new behavior
-remove presenter methods that are no longer valuable
-Moved change_password_url and login_handle_name
-up to account settings, removed them from AACs, and built
-migrations to manage the transition.
-Found and fixed all references to change_password_url on AACs
-Found and fixes all references to login_handle_name on AACs
-add datafixup for migrating AAC data to account settings
-unify repetative individual files into single form delcarations \o/
-remove old SAML editing js
-Make sure SAML still works
-Make LDAP partial flow just like SAML/CAS
-Unify position information across all types
-update "acts_as_list" to support STI classes
-move discovery URL into account auth form
-remove discover URL js management
-Unify form generation between new/existing aacs
-deprecate discovery url API endpoints
-update docs for authorization settings to deprecate their usage in AAC
api and redirect their values to current settings for now
-make delete links non-js-y to stop this silly page refresh on api
completion
-make form submissions actually submit the form rather than do this silly
page refresh on api completion
-See if anything needs “Edit Details” button, remove if not
-Wire up removing account settings by blanking out form
-Removed "cancel" button from form because fields are always open
-placate gergich
-Test removing config info
-Test population fixup on real data
-write docs for authorization settings
-fix existing specs
-fix routing and docs to not break doc generation
-fix stupid jenkins task that thinks it can’t see controls
-re-fix selenium
-fix saml debugging workflow
-write tests for acts_as_list behavior
-write tests for authorization settings
-remove auth_info types of things
-clean up and unify styles where possible
TEST PLAN:
Regression test creating/deleting/editing and logging
in with SSO solutions for CAS, LDAP, and SAML.
Should be no functional behavior modification, though
workflow will be a little more unified between the
3 currently supported types (each one will
require using the menu in the right sidebar
to add a new AAC).
Also test setting and deleting account settings through
the form underneath the configs when there are AACs in existence.
Finally, make sure that the SAML Debugging workflow still works.
Change-Id: I448db10185512d1b9469c2a425be0a3bcf9e6ebf
Reviewed-on: https://gerrit.instructure.com/53448
Tested-by: Jenkins
Reviewed-by: Cody Cutrer <cody@instructure.com>
QA-Review: August Thornton <august@instructure.com>
Product-Review: Ethan Vizitei <evizitei@instructure.com>
2015-04-28 05:23:04 +08:00
|
|
|
add_setting :login_handle_name, root_only: true
|
|
|
|
add_setting :change_password_url, root_only: true
|
2015-05-20 22:48:00 +08:00
|
|
|
add_setting :unknown_user_url, root_only: true
|
2015-03-28 01:16:08 +08:00
|
|
|
|
|
|
|
add_setting :restrict_student_future_view, :boolean => true, :default => false, :inheritable => true
|
|
|
|
add_setting :restrict_student_past_view, :boolean => true, :default => false, :inheritable => true
|
|
|
|
|
refactor user creation/invitations closes #5833
fixes #5573, #5572, #5753
* communication channels are now only unique within a single user
* UserList changes
* Always resolve pseudonym#unique_ids
* Support looking up by SMS CCs
* Option to either require e-mails match an existing CC,
or e-mails that don't match a Pseudonym will always be
returned unattached (relying on better merging behavior
to not have a gazillion accounts created)
* Method to return users, creating new ones (*without* a
Pseudonym) if necessary. (can't create with a pseudonym,
since Pseudonym#unique_id is still unique, I can't have
multiple outstanding users with the same unique_id)
* EnrollmentsFromUserList is mostly gutted, now using UserList's
functionality directy.
* Use UserList for adding account admins, removing the now
unused Account#add_admin => User#find_by_email/User#assert_by_email
codepath
* Update UsersController#create to not worry about duplicate
communication channels
* Remove AccountsController#add_user, and just use
UsersController#create
* Change SIS::UserImporter to send out a merge opportunity
e-mail if a conflicting CC is found (but still create the CC)
* In /profile, don't worry about conflicting CCs (the CC confirmation
process will now allow merging)
* Remove CommunicationChannelsController#try_merge and #merge
* For the non-simple case of CoursesController#enrollment_invitation
redirect to /register (CommunicationsChannelController#confirm)
* Remove CoursesController#transfer_enrollment
* Move PseudonymsController#registration_confirmation to
CommunicationChannelsController#confirm (have to be able to
register an account without a Pseudonym yet)
* Fold the old direct confirm functionality in, if there are
no available merge opportunities
* Allow merging the new account with the currently logged in user
* Allow changing the Pseudonym#unique_id when registering a new
account (since there might be conflicts)
* Display a list of merge opportunities based on conflicting
communication channels
* Provide link(s) to log in as the other user,
redirecting back to the registration page after login is
complete (to complete the merge as the current user)
* Remove several assert_* methods that are no longer needed
* Update PseudonymSessionsController a bit to deal with the new
way of dealing with conflicting CCs (especially CCs from LDAP),
and to redirect back to the registration/confirmation page when
attempting to do a merge
* Expose the open_registration setting; use it to control if
inviting users to a course is able to create new users
Change-Id: If2f38818a71af656854d3bf8431ddbf5dcb84691
Reviewed-on: https://gerrit.instructure.com/6149
Tested-by: Hudson <hudson@instructure.com>
Reviewed-by: Jacob Fugal <jacob@instructure.com>
2011-10-13 04:30:48 +08:00
|
|
|
add_setting :teachers_can_create_courses, :boolean => true, :root_only => true, :default => false
|
|
|
|
add_setting :students_can_create_courses, :boolean => true, :root_only => true, :default => false
|
2013-12-20 06:12:44 +08:00
|
|
|
add_setting :restrict_quiz_questions, :boolean => true, :root_only => true, :default => false
|
refactor user creation/invitations closes #5833
fixes #5573, #5572, #5753
* communication channels are now only unique within a single user
* UserList changes
* Always resolve pseudonym#unique_ids
* Support looking up by SMS CCs
* Option to either require e-mails match an existing CC,
or e-mails that don't match a Pseudonym will always be
returned unattached (relying on better merging behavior
to not have a gazillion accounts created)
* Method to return users, creating new ones (*without* a
Pseudonym) if necessary. (can't create with a pseudonym,
since Pseudonym#unique_id is still unique, I can't have
multiple outstanding users with the same unique_id)
* EnrollmentsFromUserList is mostly gutted, now using UserList's
functionality directy.
* Use UserList for adding account admins, removing the now
unused Account#add_admin => User#find_by_email/User#assert_by_email
codepath
* Update UsersController#create to not worry about duplicate
communication channels
* Remove AccountsController#add_user, and just use
UsersController#create
* Change SIS::UserImporter to send out a merge opportunity
e-mail if a conflicting CC is found (but still create the CC)
* In /profile, don't worry about conflicting CCs (the CC confirmation
process will now allow merging)
* Remove CommunicationChannelsController#try_merge and #merge
* For the non-simple case of CoursesController#enrollment_invitation
redirect to /register (CommunicationsChannelController#confirm)
* Remove CoursesController#transfer_enrollment
* Move PseudonymsController#registration_confirmation to
CommunicationChannelsController#confirm (have to be able to
register an account without a Pseudonym yet)
* Fold the old direct confirm functionality in, if there are
no available merge opportunities
* Allow merging the new account with the currently logged in user
* Allow changing the Pseudonym#unique_id when registering a new
account (since there might be conflicts)
* Display a list of merge opportunities based on conflicting
communication channels
* Provide link(s) to log in as the other user,
redirecting back to the registration page after login is
complete (to complete the merge as the current user)
* Remove several assert_* methods that are no longer needed
* Update PseudonymSessionsController a bit to deal with the new
way of dealing with conflicting CCs (especially CCs from LDAP),
and to redirect back to the registration/confirmation page when
attempting to do a merge
* Expose the open_registration setting; use it to control if
inviting users to a course is able to create new users
Change-Id: If2f38818a71af656854d3bf8431ddbf5dcb84691
Reviewed-on: https://gerrit.instructure.com/6149
Tested-by: Hudson <hudson@instructure.com>
Reviewed-by: Jacob Fugal <jacob@instructure.com>
2011-10-13 04:30:48 +08:00
|
|
|
add_setting :no_enrollments_can_create_courses, :boolean => true, :root_only => true, :default => false
|
2011-05-04 11:16:50 +08:00
|
|
|
add_setting :allow_sending_scores_in_emails, :boolean => true, :root_only => true
|
2011-02-01 09:57:29 +08:00
|
|
|
add_setting :support_url, :root_only => true
|
2011-03-27 11:38:54 +08:00
|
|
|
add_setting :self_enrollment
|
2011-02-03 05:30:07 +08:00
|
|
|
add_setting :equella_endpoint
|
|
|
|
add_setting :equella_teaser
|
2011-07-26 00:12:55 +08:00
|
|
|
add_setting :enable_alerts, :boolean => true, :root_only => true
|
2011-08-24 03:44:01 +08:00
|
|
|
add_setting :enable_eportfolios, :boolean => true, :root_only => true
|
2011-10-06 23:54:05 +08:00
|
|
|
add_setting :users_can_edit_name, :boolean => true, :root_only => true
|
allow disabling canvas auth for accounts with another auth source
disabling canvas auth also force-disables open registration, and
makes LDAP auth act like full delegated auth (CAS or SAML)
test plan:
* configure LDAP, CAS, or SAML. MAKE SURE YOU CAN LOG IN.
* go to account settings, and disable "Canvas Authentication"
* open registration should no longer show up on account settings
page (after saving)
* ensure you can no longer log in with your Canvas credentials, but
you can with LDAP, CAS, or SAML credentials.
* remove LDAP, CAS, or SAML from the account
* "Canvas Authentication" should no longer show up on the account
settings page, open registration should
* your Canvas credentials should start working again
* add LDAP, CAS, or SAML back
* "Canvas Authentication" should be back on in account settings
Change-Id: Ic7475623e5139bb545a87d8e5b1014dabaf4e854
Reviewed-on: https://gerrit.instructure.com/12850
Reviewed-by: Jeremy Stanley <jeremy@instructure.com>
Tested-by: Jenkins <jenkins@instructure.com>
2012-08-11 01:17:08 +08:00
|
|
|
add_setting :open_registration, :boolean => true, :root_only => true
|
2012-11-13 05:12:57 +08:00
|
|
|
add_setting :show_scheduler, :boolean => true, :root_only => true, :default => false
|
2012-07-11 05:03:14 +08:00
|
|
|
add_setting :enable_profiles, :boolean => true, :root_only => true, :default => false
|
2013-12-07 05:57:58 +08:00
|
|
|
add_setting :enable_manage_groups2, :boolean => true, :root_only => true, :default => true
|
multi-factor authentication closes #9532
test plan:
* enable optional MFA, and check the following:
* normal log in should not be affected
* you can enroll in MFA from your profile page
* you can re-enroll in MFA from your profile page
* you can disable MFA from your profile page
* MFA can be reset by an admin on your user page
* when enrolled, you are asked for verification code after
username/password when logging in
* you can't access any other part of the site directly until
until entering your verification code
* enable required MFA, and check the following
* when not enrolled in MFA, and you log in, you are forced to
enroll
* you cannot disable MFA from your profile page
* you can re-enroll in MFA from your profile page
* an admin (other than himself) can reset MFA from the user page
* for enrolling in MFA
* use Google Authenticator and scan the QR code; you should have
30-seconds or so of extra leeway to enter your code
* having no SMS communication channels on your profile, the
enrollment page should just have a form to add a new phone
* having one or more SMS communication channels on your profile,
the enrollment page should list them, or allow you to create
a new one (and switch back)
* having more than one SMS communication channel on your profile,
the enrollment page should remember which one you have selected
after you click "send"
* an unconfirmed SMS channel should go to confirmed when it's used
to enroll in MFA
* you should not be able to go directly to /login/otp to enroll
if you used "Remember me" token to log in
* MFA login flow
* if configured with SMS, it should send you an SMS after you
put in your username/password; you should have about 5 minutes
of leeway to put it in
* if you don't check "remember computer" checkbox, you should have
to enter a verification code each time you log in
* if you do check it, you shouldn't have to enter your code
anymore (for three days). it also shouldn't SMS you a
verification code each time you log in
* setting MFA to required for admins should make it required for
admins, optional for other users
* with MFA enabled, directly go to /login/otp after entering
username/password but before entering a verification code; it
should send you back to the main login page
* if you enrolled via SMS, you should not be able to remove that
SMS from your profile
* there should not be a reset MFA link on a user page if they
haven't enrolled
* test a login or required enrollment sequence with CAS and/or SAML
Change-Id: I692de7405bf7ca023183e717930ee940ccf0d5e6
Reviewed-on: https://gerrit.instructure.com/12700
Tested-by: Jenkins <jenkins@instructure.com>
Reviewed-by: Brian Palmer <brianp@instructure.com>
2012-08-03 05:17:50 +08:00
|
|
|
add_setting :mfa_settings, :root_only => true
|
allow disabling canvas auth for accounts with another auth source
disabling canvas auth also force-disables open registration, and
makes LDAP auth act like full delegated auth (CAS or SAML)
test plan:
* configure LDAP, CAS, or SAML. MAKE SURE YOU CAN LOG IN.
* go to account settings, and disable "Canvas Authentication"
* open registration should no longer show up on account settings
page (after saving)
* ensure you can no longer log in with your Canvas credentials, but
you can with LDAP, CAS, or SAML credentials.
* remove LDAP, CAS, or SAML from the account
* "Canvas Authentication" should no longer show up on the account
settings page, open registration should
* your Canvas credentials should start working again
* add LDAP, CAS, or SAML back
* "Canvas Authentication" should be back on in account settings
Change-Id: Ic7475623e5139bb545a87d8e5b1014dabaf4e854
Reviewed-on: https://gerrit.instructure.com/12850
Reviewed-by: Jeremy Stanley <jeremy@instructure.com>
Tested-by: Jenkins <jenkins@instructure.com>
2012-08-11 01:17:08 +08:00
|
|
|
add_setting :canvas_authentication, :boolean => true, :root_only => true
|
2012-10-23 12:55:00 +08:00
|
|
|
add_setting :admins_can_change_passwords, :boolean => true, :root_only => true, :default => false
|
2013-03-15 03:36:27 +08:00
|
|
|
add_setting :admins_can_view_notifications, :boolean => true, :root_only => true, :default => false
|
2012-10-25 06:35:57 +08:00
|
|
|
add_setting :outgoing_email_default_name
|
2012-12-06 06:24:42 +08:00
|
|
|
add_setting :external_notification_warning, :boolean => true, :default => false
|
add root account setting for custom terms/privacy
fixes CNVS-6888
testing steps:
* from a console, set a custom privacy and terms url
Setting.set('privacy_policy_url', 'http://custom.example.com/privacy')
Setting.set('terms_of_use_url', 'http://custom.example.com/terms')
* verify the following location all display the new links:
* <canvas>/enroll/<join_code> - with self_enrollments enabled for
the course and currently logged out
* when self registration is disabled at the root account,
will be prompted to authenticate, "View Privacy Policy"
* when self registration is enabled at the root account,
will be prompted to authenticate or register, "View Privacy Policy"
* when logged in as a teacher, prompted to confirm enrollment
"View Privacy Policy"
* <canvas>/register_from_website - when logged out
* page footer links for "Terms of Use" and "Privacy Policy"
* "I'm a teacher", both links at bottom of dialog
* "I'm a student", both links at bottom of dialog
* "Parents sign up here", both links at bottom of dialog
* <canvas>/accounts/<id>/users - click "Add a New User" from
the right sidebar. "View Privacy Policy" in modal dialog.
Change-Id: Ic80c369dcea8483c93fb8adfcd9a80c1d6d3ad6a
Reviewed-on: https://gerrit.instructure.com/22712
Reviewed-by: Jon Jensen <jon@instructure.com>
Tested-by: Jenkins <jenkins@instructure.com>
QA-Review: Marc LeGendre <marc@instructure.com>
Product-Review: Marc LeGendre <marc@instructure.com>
2013-07-30 01:50:51 +08:00
|
|
|
# Terms of Use and Privacy Policy settings for the root account
|
|
|
|
add_setting :terms_changed_at, :root_only => true
|
2012-11-28 05:37:32 +08:00
|
|
|
# When a user is invited to a course, do we let them see a preview of the
|
|
|
|
# course even without registering? This is part of the free-for-teacher
|
|
|
|
# account perks, since anyone can invite anyone to join any course, and it'd
|
|
|
|
# be nice to be able to see the course first if you weren't expecting the
|
|
|
|
# invitation.
|
|
|
|
add_setting :allow_invitation_previews, :boolean => true, :root_only => true, :default => false
|
2013-01-11 05:29:19 +08:00
|
|
|
add_setting :self_registration, :boolean => true, :root_only => true, :default => false
|
2013-11-26 05:13:51 +08:00
|
|
|
# if self_registration_type is 'observer', then only observers (i.e. parents) can self register.
|
2014-02-22 04:05:54 +08:00
|
|
|
# if self_registration_type is 'all' or nil, any user type can self register.
|
2013-11-26 05:13:51 +08:00
|
|
|
add_setting :self_registration_type, :root_only => true
|
2013-02-12 03:52:53 +08:00
|
|
|
add_setting :large_course_rosters, :boolean => true, :root_only => true, :default => false
|
2013-02-26 05:32:41 +08:00
|
|
|
add_setting :edit_institution_email, :boolean => true, :root_only => true, :default => true
|
2014-02-15 05:08:45 +08:00
|
|
|
add_setting :js_kaltura_uploader, :boolean => true, :root_only => true, :default => false
|
2013-11-19 03:41:10 +08:00
|
|
|
add_setting :google_docs_domain, root_only: true
|
2014-03-18 04:25:44 +08:00
|
|
|
add_setting :dashboard_url, root_only: true
|
2014-04-25 17:12:45 +08:00
|
|
|
add_setting :product_name, root_only: true
|
2014-09-09 06:47:27 +08:00
|
|
|
add_setting :author_email_in_notifications, boolean: true, root_only: true, default: false
|
2015-05-26 15:24:58 +08:00
|
|
|
add_setting :include_students_in_global_survey, boolean: true, root_only: true, default: false
|
2014-12-16 02:43:45 +08:00
|
|
|
add_setting :trusted_referers, root_only: true
|
2011-11-22 03:28:20 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def settings=(hash)
|
2015-07-01 23:31:41 +08:00
|
|
|
@invalidate_settings_cache = true
|
2011-02-01 09:57:29 +08:00
|
|
|
if hash.is_a?(Hash)
|
|
|
|
hash.each do |key, val|
|
|
|
|
if account_settings_options && account_settings_options[key.to_sym]
|
|
|
|
opts = account_settings_options[key.to_sym]
|
2011-12-28 05:38:15 +08:00
|
|
|
if (opts[:root_only] && !self.root_account?) || (opts[:condition] && !self.send("#{opts[:condition]}?".to_sym))
|
2011-02-01 09:57:29 +08:00
|
|
|
settings.delete key.to_sym
|
|
|
|
elsif opts[:hash]
|
|
|
|
new_hash = {}
|
|
|
|
if val.is_a?(Hash)
|
|
|
|
val.each do |inner_key, inner_val|
|
2015-03-28 01:16:08 +08:00
|
|
|
inner_key = inner_key.to_sym
|
|
|
|
if opts[:values].include?(inner_key)
|
|
|
|
if opts[:inheritable] && (inner_key == :locked || (inner_key == :value && opts[:boolean]))
|
|
|
|
new_hash[inner_key] = Canvas::Plugin.value_to_boolean(inner_val)
|
|
|
|
else
|
|
|
|
new_hash[inner_key] = inner_val.to_s
|
|
|
|
end
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
settings[key.to_sym] = new_hash.empty? ? nil : new_hash
|
2015-03-28 01:16:08 +08:00
|
|
|
elsif opts[:boolean]
|
|
|
|
settings[key.to_sym] = Canvas::Plugin.value_to_boolean(val)
|
2011-02-01 09:57:29 +08:00
|
|
|
else
|
|
|
|
settings[key.to_sym] = val.to_s
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2015-02-06 08:54:38 +08:00
|
|
|
# prune nil or "" hash values to save space in the DB.
|
|
|
|
settings.reject! { |_, value| value.nil? || value == "" }
|
2011-02-01 09:57:29 +08:00
|
|
|
settings
|
|
|
|
end
|
2012-05-01 06:14:39 +08:00
|
|
|
|
2014-04-25 17:12:45 +08:00
|
|
|
def product_name
|
|
|
|
settings[:product_name] || t("#product_name", "Canvas")
|
|
|
|
end
|
|
|
|
|
sub-account branding; closes #9368
allow sub accounts to include their own global scripts and stylesheets. if
global includes are enabled on the root account, root account administrators
will have an option to enable them for immediate child accounts. those child
accounts can then choose to enable them for their sub-accounts, and so on down
the chain.
these includes are added to the page in order from highest to lowest account,
so sub-accounts are able to override styles added by their parents.
the logic for which styles to display on which pages is as follows:
- on account pages, include all styles in the chain from this account up to the
root account. this ensures that you can always see styles for account
X without any sub-account overrides on account X's page
- on course/group pages, include all styles in the chain from the account which
contains that course/group up to the root
- on the dashboard, calendar, user pages, and other pages that don't fall into
one of the above categories, we find the lowest account that contains all of
the current user's active classes + groups, and include styles from that
account up to the root
test plan:
- in a root account, create two sub-accounts, create courses in each of them,
and create 3 users, one enrolled only in the first course, one only in the
second course, and one enrolled in both courses.
- enable global includes on the root account (no sub-accounts yet) add files,
and make sure all three students see them.
- now enable sub-account includes, and add include files to each sub-account
- make sure both users in course 1 see include for sub-account 1
- make sure user 1 sees include for sub-account 1 on her dashboard, but user
3 does not.
Change-Id: I3d07d4bced39593f3084d5eac6ea3137666e319b
Reviewed-on: https://gerrit.instructure.com/12248
Tested-by: Jenkins <jenkins@instructure.com>
Reviewed-by: Simon Williams <simon@instructure.com>
2012-07-10 05:30:16 +08:00
|
|
|
def allow_global_includes?
|
|
|
|
self.global_includes? || self.parent_account.try(:sub_account_includes?)
|
|
|
|
end
|
|
|
|
|
2012-08-19 02:37:31 +08:00
|
|
|
def global_includes_hash
|
|
|
|
includes = {}
|
|
|
|
if allow_global_includes?
|
|
|
|
includes = {}
|
|
|
|
includes[:js] = settings[:global_javascript] if settings[:global_javascript].present?
|
|
|
|
includes[:css] = settings[:global_stylesheet] if settings[:global_stylesheet].present?
|
|
|
|
end
|
|
|
|
includes.present? ? includes : nil
|
|
|
|
end
|
|
|
|
|
multi-factor authentication closes #9532
test plan:
* enable optional MFA, and check the following:
* normal log in should not be affected
* you can enroll in MFA from your profile page
* you can re-enroll in MFA from your profile page
* you can disable MFA from your profile page
* MFA can be reset by an admin on your user page
* when enrolled, you are asked for verification code after
username/password when logging in
* you can't access any other part of the site directly until
until entering your verification code
* enable required MFA, and check the following
* when not enrolled in MFA, and you log in, you are forced to
enroll
* you cannot disable MFA from your profile page
* you can re-enroll in MFA from your profile page
* an admin (other than himself) can reset MFA from the user page
* for enrolling in MFA
* use Google Authenticator and scan the QR code; you should have
30-seconds or so of extra leeway to enter your code
* having no SMS communication channels on your profile, the
enrollment page should just have a form to add a new phone
* having one or more SMS communication channels on your profile,
the enrollment page should list them, or allow you to create
a new one (and switch back)
* having more than one SMS communication channel on your profile,
the enrollment page should remember which one you have selected
after you click "send"
* an unconfirmed SMS channel should go to confirmed when it's used
to enroll in MFA
* you should not be able to go directly to /login/otp to enroll
if you used "Remember me" token to log in
* MFA login flow
* if configured with SMS, it should send you an SMS after you
put in your username/password; you should have about 5 minutes
of leeway to put it in
* if you don't check "remember computer" checkbox, you should have
to enter a verification code each time you log in
* if you do check it, you shouldn't have to enter your code
anymore (for three days). it also shouldn't SMS you a
verification code each time you log in
* setting MFA to required for admins should make it required for
admins, optional for other users
* with MFA enabled, directly go to /login/otp after entering
username/password but before entering a verification code; it
should send you back to the main login page
* if you enrolled via SMS, you should not be able to remove that
SMS from your profile
* there should not be a reset MFA link on a user page if they
haven't enrolled
* test a login or required enrollment sequence with CAS and/or SAML
Change-Id: I692de7405bf7ca023183e717930ee940ccf0d5e6
Reviewed-on: https://gerrit.instructure.com/12700
Tested-by: Jenkins <jenkins@instructure.com>
Reviewed-by: Brian Palmer <brianp@instructure.com>
2012-08-03 05:17:50 +08:00
|
|
|
def mfa_settings
|
|
|
|
settings[:mfa_settings].try(:to_sym) || :disabled
|
|
|
|
end
|
|
|
|
|
2015-05-28 05:20:41 +08:00
|
|
|
def non_canvas_auth_configured?
|
2015-06-05 00:42:53 +08:00
|
|
|
authentication_providers.active.exists?
|
2015-05-28 05:20:41 +08:00
|
|
|
end
|
|
|
|
|
allow disabling canvas auth for accounts with another auth source
disabling canvas auth also force-disables open registration, and
makes LDAP auth act like full delegated auth (CAS or SAML)
test plan:
* configure LDAP, CAS, or SAML. MAKE SURE YOU CAN LOG IN.
* go to account settings, and disable "Canvas Authentication"
* open registration should no longer show up on account settings
page (after saving)
* ensure you can no longer log in with your Canvas credentials, but
you can with LDAP, CAS, or SAML credentials.
* remove LDAP, CAS, or SAML from the account
* "Canvas Authentication" should no longer show up on the account
settings page, open registration should
* your Canvas credentials should start working again
* add LDAP, CAS, or SAML back
* "Canvas Authentication" should be back on in account settings
Change-Id: Ic7475623e5139bb545a87d8e5b1014dabaf4e854
Reviewed-on: https://gerrit.instructure.com/12850
Reviewed-by: Jeremy Stanley <jeremy@instructure.com>
Tested-by: Jenkins <jenkins@instructure.com>
2012-08-11 01:17:08 +08:00
|
|
|
def canvas_authentication?
|
2015-05-28 05:20:41 +08:00
|
|
|
settings[:canvas_authentication] != false || !non_canvas_auth_configured?
|
allow disabling canvas auth for accounts with another auth source
disabling canvas auth also force-disables open registration, and
makes LDAP auth act like full delegated auth (CAS or SAML)
test plan:
* configure LDAP, CAS, or SAML. MAKE SURE YOU CAN LOG IN.
* go to account settings, and disable "Canvas Authentication"
* open registration should no longer show up on account settings
page (after saving)
* ensure you can no longer log in with your Canvas credentials, but
you can with LDAP, CAS, or SAML credentials.
* remove LDAP, CAS, or SAML from the account
* "Canvas Authentication" should no longer show up on the account
settings page, open registration should
* your Canvas credentials should start working again
* add LDAP, CAS, or SAML back
* "Canvas Authentication" should be back on in account settings
Change-Id: Ic7475623e5139bb545a87d8e5b1014dabaf4e854
Reviewed-on: https://gerrit.instructure.com/12850
Reviewed-by: Jeremy Stanley <jeremy@instructure.com>
Tested-by: Jenkins <jenkins@instructure.com>
2012-08-11 01:17:08 +08:00
|
|
|
end
|
|
|
|
|
2015-06-05 00:42:53 +08:00
|
|
|
def enable_canvas_authentication
|
|
|
|
return if settings[:canvas_authentication]
|
|
|
|
settings[:canvas_authentication] = true
|
|
|
|
self.save!
|
|
|
|
end
|
|
|
|
|
allow disabling canvas auth for accounts with another auth source
disabling canvas auth also force-disables open registration, and
makes LDAP auth act like full delegated auth (CAS or SAML)
test plan:
* configure LDAP, CAS, or SAML. MAKE SURE YOU CAN LOG IN.
* go to account settings, and disable "Canvas Authentication"
* open registration should no longer show up on account settings
page (after saving)
* ensure you can no longer log in with your Canvas credentials, but
you can with LDAP, CAS, or SAML credentials.
* remove LDAP, CAS, or SAML from the account
* "Canvas Authentication" should no longer show up on the account
settings page, open registration should
* your Canvas credentials should start working again
* add LDAP, CAS, or SAML back
* "Canvas Authentication" should be back on in account settings
Change-Id: Ic7475623e5139bb545a87d8e5b1014dabaf4e854
Reviewed-on: https://gerrit.instructure.com/12850
Reviewed-by: Jeremy Stanley <jeremy@instructure.com>
Tested-by: Jenkins <jenkins@instructure.com>
2012-08-11 01:17:08 +08:00
|
|
|
def open_registration?
|
|
|
|
!!settings[:open_registration] && canvas_authentication?
|
|
|
|
end
|
|
|
|
|
2013-01-11 05:29:19 +08:00
|
|
|
def self_registration?
|
|
|
|
!!settings[:self_registration] && canvas_authentication?
|
|
|
|
end
|
|
|
|
|
2013-11-26 05:13:51 +08:00
|
|
|
def self_registration_type
|
|
|
|
settings[:self_registration_type]
|
|
|
|
end
|
|
|
|
|
|
|
|
def self_registration_allowed_for?(type)
|
|
|
|
return false unless self_registration?
|
2014-02-22 04:05:54 +08:00
|
|
|
return false if self_registration_type && self_registration_type != 'all' && type != self_registration_type
|
2013-11-26 05:13:51 +08:00
|
|
|
true
|
|
|
|
end
|
|
|
|
|
add root account setting for custom terms/privacy
fixes CNVS-6888
testing steps:
* from a console, set a custom privacy and terms url
Setting.set('privacy_policy_url', 'http://custom.example.com/privacy')
Setting.set('terms_of_use_url', 'http://custom.example.com/terms')
* verify the following location all display the new links:
* <canvas>/enroll/<join_code> - with self_enrollments enabled for
the course and currently logged out
* when self registration is disabled at the root account,
will be prompted to authenticate, "View Privacy Policy"
* when self registration is enabled at the root account,
will be prompted to authenticate or register, "View Privacy Policy"
* when logged in as a teacher, prompted to confirm enrollment
"View Privacy Policy"
* <canvas>/register_from_website - when logged out
* page footer links for "Terms of Use" and "Privacy Policy"
* "I'm a teacher", both links at bottom of dialog
* "I'm a student", both links at bottom of dialog
* "Parents sign up here", both links at bottom of dialog
* <canvas>/accounts/<id>/users - click "Add a New User" from
the right sidebar. "View Privacy Policy" in modal dialog.
Change-Id: Ic80c369dcea8483c93fb8adfcd9a80c1d6d3ad6a
Reviewed-on: https://gerrit.instructure.com/22712
Reviewed-by: Jon Jensen <jon@instructure.com>
Tested-by: Jenkins <jenkins@instructure.com>
QA-Review: Marc LeGendre <marc@instructure.com>
Product-Review: Marc LeGendre <marc@instructure.com>
2013-07-30 01:50:51 +08:00
|
|
|
def terms_required?
|
2013-10-05 04:02:49 +08:00
|
|
|
Setting.get('terms_required', 'true') == 'true'
|
add root account setting for custom terms/privacy
fixes CNVS-6888
testing steps:
* from a console, set a custom privacy and terms url
Setting.set('privacy_policy_url', 'http://custom.example.com/privacy')
Setting.set('terms_of_use_url', 'http://custom.example.com/terms')
* verify the following location all display the new links:
* <canvas>/enroll/<join_code> - with self_enrollments enabled for
the course and currently logged out
* when self registration is disabled at the root account,
will be prompted to authenticate, "View Privacy Policy"
* when self registration is enabled at the root account,
will be prompted to authenticate or register, "View Privacy Policy"
* when logged in as a teacher, prompted to confirm enrollment
"View Privacy Policy"
* <canvas>/register_from_website - when logged out
* page footer links for "Terms of Use" and "Privacy Policy"
* "I'm a teacher", both links at bottom of dialog
* "I'm a student", both links at bottom of dialog
* "Parents sign up here", both links at bottom of dialog
* <canvas>/accounts/<id>/users - click "Add a New User" from
the right sidebar. "View Privacy Policy" in modal dialog.
Change-Id: Ic80c369dcea8483c93fb8adfcd9a80c1d6d3ad6a
Reviewed-on: https://gerrit.instructure.com/22712
Reviewed-by: Jon Jensen <jon@instructure.com>
Tested-by: Jenkins <jenkins@instructure.com>
QA-Review: Marc LeGendre <marc@instructure.com>
Product-Review: Marc LeGendre <marc@instructure.com>
2013-07-30 01:50:51 +08:00
|
|
|
end
|
|
|
|
|
2013-07-30 05:28:02 +08:00
|
|
|
def require_acceptance_of_terms?(user)
|
2015-05-05 22:43:52 +08:00
|
|
|
soc2_start_date = Setting.get('SOC2_start_date', Time.new(2015, 5, 16, 0, 0, 0).utc).to_datetime
|
2015-02-20 08:44:03 +08:00
|
|
|
|
2013-07-30 05:28:02 +08:00
|
|
|
return false if !terms_required?
|
|
|
|
return true if user.nil? || user.new_record?
|
|
|
|
terms_changed_at = settings[:terms_changed_at]
|
|
|
|
last_accepted = user.preferences[:accepted_terms]
|
2015-02-20 08:44:03 +08:00
|
|
|
|
|
|
|
# make sure existing users are grandfathered in
|
|
|
|
return false if terms_changed_at.nil? && user.registered? && user.created_at < soc2_start_date
|
|
|
|
|
2013-07-31 03:50:49 +08:00
|
|
|
return false if last_accepted && (terms_changed_at.nil? || last_accepted > terms_changed_at)
|
2013-07-30 05:28:02 +08:00
|
|
|
true
|
|
|
|
end
|
|
|
|
|
2011-01-18 14:39:47 +08:00
|
|
|
def ip_filters=(params)
|
|
|
|
filters = {}
|
|
|
|
require 'ipaddr'
|
|
|
|
params.each do |key, str|
|
|
|
|
ips = []
|
|
|
|
vals = str.split(/,/)
|
|
|
|
vals.each do |val|
|
|
|
|
ip = IPAddr.new(val) rescue nil
|
|
|
|
# right now the ip_filter column on quizzes is just a string,
|
|
|
|
# so it has a max length. I figure whatever we set it to this
|
|
|
|
# setter should at the very least limit stored values to that
|
|
|
|
# length.
|
2014-05-03 00:35:29 +08:00
|
|
|
ips << val if ip && val.length <= 255
|
2011-01-18 14:39:47 +08:00
|
|
|
end
|
|
|
|
filters[key] = ips.join(',') unless ips.empty?
|
|
|
|
end
|
|
|
|
settings[:ip_filters] = filters
|
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def ensure_defaults
|
2014-07-11 01:22:01 +08:00
|
|
|
self.uuid ||= CanvasSlug.generate_securish_uuid
|
2014-05-20 05:37:01 +08:00
|
|
|
self.lti_guid ||= "#{self.uuid}:#{INSTANCE_GUID_SUFFIX}" if self.respond_to?(:lti_guid)
|
2015-01-14 04:10:14 +08:00
|
|
|
self.root_account_id ||= self.parent_account.root_account_id if self.parent_account
|
|
|
|
self.root_account_id ||= self.parent_account_id
|
|
|
|
self.parent_account_id ||= self.root_account_id
|
|
|
|
Account.invalidate_cache(self.id) if self.id
|
|
|
|
true
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
2013-03-11 05:49:17 +08:00
|
|
|
|
2011-06-01 04:47:28 +08:00
|
|
|
def verify_unique_sis_source_id
|
|
|
|
return true unless self.sis_source_id
|
2015-01-14 01:50:19 +08:00
|
|
|
return true if !root_account_id_changed? && !sis_source_id_changed?
|
|
|
|
|
2011-06-01 04:47:28 +08:00
|
|
|
if self.root_account?
|
2011-11-17 00:47:54 +08:00
|
|
|
self.errors.add(:sis_source_id, t('#account.root_account_cant_have_sis_id', "SIS IDs cannot be set on root accounts"))
|
|
|
|
return false
|
2011-06-01 04:47:28 +08:00
|
|
|
end
|
2011-11-17 00:47:54 +08:00
|
|
|
|
2015-01-14 01:50:19 +08:00
|
|
|
scope = root_account.all_accounts.where(sis_source_id: self.sis_source_id)
|
|
|
|
scope = scope.where("id<>?", self) unless self.new_record?
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2015-01-14 01:50:19 +08:00
|
|
|
return true unless scope.exists?
|
2011-11-17 00:47:54 +08:00
|
|
|
|
2011-06-16 04:39:32 +08:00
|
|
|
self.errors.add(:sis_source_id, t('#account.sis_id_in_use', "SIS ID \"%{sis_id}\" is already in use", :sis_id => self.sis_source_id))
|
2011-06-01 04:47:28 +08:00
|
|
|
false
|
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def update_account_associations_if_changed
|
2015-01-14 04:10:14 +08:00
|
|
|
send_later_if_production(:update_account_associations) if self.parent_account_id_changed? || self.root_account_id_changed?
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def equella_settings
|
2011-02-03 05:30:07 +08:00
|
|
|
endpoint = self.settings[:equella_endpoint] || self.equella_endpoint
|
|
|
|
if !endpoint.blank?
|
2011-02-01 09:57:29 +08:00
|
|
|
OpenObject.new({
|
2011-02-03 05:30:07 +08:00
|
|
|
:endpoint => endpoint,
|
|
|
|
:default_action => self.settings[:equella_action] || 'selectOrAdd',
|
|
|
|
:teaser => self.settings[:equella_teaser]
|
2011-02-01 09:57:29 +08:00
|
|
|
})
|
2011-02-03 05:30:07 +08:00
|
|
|
else
|
|
|
|
nil
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def settings
|
2011-08-05 03:10:53 +08:00
|
|
|
result = self.read_attribute(:settings)
|
|
|
|
return result if result
|
2013-03-22 07:32:21 +08:00
|
|
|
return write_attribute(:settings, {}) unless frozen?
|
2011-08-05 03:10:53 +08:00
|
|
|
{}.freeze
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2011-04-27 23:30:56 +08:00
|
|
|
def domain
|
|
|
|
HostUrl.context_host(self)
|
|
|
|
end
|
2013-12-24 03:01:21 +08:00
|
|
|
|
|
|
|
def self.find_by_domain(domain)
|
|
|
|
self.default if HostUrl.default_host == domain
|
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2011-06-01 04:47:28 +08:00
|
|
|
def root_account?
|
|
|
|
!self.root_account_id
|
|
|
|
end
|
2011-12-28 05:57:56 +08:00
|
|
|
|
|
|
|
def root_account_with_self
|
|
|
|
return self if self.root_account?
|
|
|
|
root_account_without_self
|
|
|
|
end
|
|
|
|
alias_method_chain :root_account, :self
|
|
|
|
|
2011-08-05 23:38:31 +08:00
|
|
|
def sub_accounts_as_options(indent = 0, preloaded_accounts = nil)
|
|
|
|
unless preloaded_accounts
|
|
|
|
preloaded_accounts = {}
|
2011-12-28 05:57:56 +08:00
|
|
|
self.root_account.all_accounts.active.each do |account|
|
2011-08-05 23:38:31 +08:00
|
|
|
(preloaded_accounts[account.parent_account_id] ||= []) << account
|
|
|
|
end
|
|
|
|
end
|
2011-02-23 03:38:54 +08:00
|
|
|
res = [[(" " * indent).html_safe + self.name, self.id]]
|
2011-08-05 23:38:31 +08:00
|
|
|
if preloaded_accounts[self.id]
|
|
|
|
preloaded_accounts[self.id].each do |account|
|
|
|
|
res += account.sub_accounts_as_options(indent + 1, preloaded_accounts)
|
|
|
|
end
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
|
|
|
res
|
|
|
|
end
|
2013-06-07 20:59:27 +08:00
|
|
|
|
|
|
|
def users_visible_to(user)
|
2014-05-03 00:35:29 +08:00
|
|
|
self.grants_right?(user, :read) ? self.all_users : self.all_users.none
|
2013-06-07 20:59:27 +08:00
|
|
|
end
|
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def users_name_like(query="")
|
|
|
|
@cached_users_name_like ||= {}
|
|
|
|
@cached_users_name_like[query] ||= self.fast_all_users.name_like(query)
|
|
|
|
end
|
2011-05-27 06:23:06 +08:00
|
|
|
|
2013-06-04 05:04:55 +08:00
|
|
|
def associated_courses
|
2015-05-13 03:38:49 +08:00
|
|
|
if root_account?
|
|
|
|
all_courses
|
|
|
|
else
|
|
|
|
shard.activate do
|
|
|
|
Course.where("EXISTS (SELECT 1 FROM course_account_associations WHERE course_id=courses.id AND account_id=?)", self)
|
|
|
|
end
|
2014-03-10 23:47:26 +08:00
|
|
|
end
|
2013-06-04 05:04:55 +08:00
|
|
|
end
|
|
|
|
|
2014-06-18 05:32:18 +08:00
|
|
|
def associated_user?(user)
|
|
|
|
user_account_associations.where(user_id: user).exists?
|
|
|
|
end
|
|
|
|
|
2011-05-27 06:23:06 +08:00
|
|
|
def fast_course_base(opts)
|
2012-04-06 03:23:56 +08:00
|
|
|
columns = "courses.id, courses.name, courses.workflow_state, courses.course_code, courses.sis_source_id, courses.enrollment_term_id"
|
2011-05-27 06:23:06 +08:00
|
|
|
associated_courses = self.associated_courses.active
|
2011-08-17 05:49:53 +08:00
|
|
|
associated_courses = associated_courses.with_enrollments if opts[:hide_enrollmentless_courses]
|
2011-05-27 06:23:06 +08:00
|
|
|
associated_courses = associated_courses.for_term(opts[:term]) if opts[:term].present?
|
2011-06-28 08:01:05 +08:00
|
|
|
associated_courses = yield associated_courses if block_given?
|
2013-06-04 05:04:55 +08:00
|
|
|
associated_courses.limit(opts[:limit]).active_first.select(columns).all
|
2011-05-27 06:23:06 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def fast_all_courses(opts={})
|
2011-02-01 09:57:29 +08:00
|
|
|
@cached_fast_all_courses ||= {}
|
2011-06-28 08:01:05 +08:00
|
|
|
@cached_fast_all_courses[opts] ||= self.fast_course_base(opts)
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
2011-05-27 06:23:06 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def all_users(limit=250)
|
|
|
|
@cached_all_users ||= {}
|
2013-03-19 03:07:47 +08:00
|
|
|
@cached_all_users[limit] ||= User.of_account(self).limit(limit)
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def fast_all_users(limit=nil)
|
|
|
|
@cached_fast_all_users ||= {}
|
2013-03-08 07:23:32 +08:00
|
|
|
@cached_fast_all_users[limit] ||= self.all_users(limit).active.select("users.id, users.name, users.sortable_name").order_by_sortable_name
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
2011-10-29 07:19:11 +08:00
|
|
|
|
2013-11-21 08:15:12 +08:00
|
|
|
def users_not_in_groups(groups, opts={})
|
|
|
|
scope = User.active.joins(:user_account_associations).
|
|
|
|
where(user_account_associations: {account_id: self}).
|
|
|
|
where(Group.not_in_group_sql_fragment(groups.map(&:id))).
|
|
|
|
select("users.id, users.name")
|
|
|
|
scope = scope.select(opts[:order]).order(opts[:order]) if opts[:order]
|
|
|
|
scope
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
2011-05-27 06:23:06 +08:00
|
|
|
|
|
|
|
def courses_name_like(query="", opts={})
|
|
|
|
opts[:limit] ||= 200
|
|
|
|
@cached_courses_name_like ||= {}
|
|
|
|
@cached_courses_name_like[[query, opts]] ||= self.fast_course_base(opts) {|q| q.name_like(query)}
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
|
|
|
|
2012-12-07 14:28:37 +08:00
|
|
|
def self_enrollment_course_for(code)
|
|
|
|
all_courses.
|
2013-01-29 08:36:31 +08:00
|
|
|
where(:self_enrollment_code => code).
|
2012-12-07 14:28:37 +08:00
|
|
|
first
|
|
|
|
end
|
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def file_namespace
|
2013-04-19 02:34:50 +08:00
|
|
|
Shard.birth.activate { "account_#{self.root_account.id}" }
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def self.account_lookup_cache_key(id)
|
2011-09-28 04:36:33 +08:00
|
|
|
['_account_lookup2', id].cache_key
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def self.invalidate_cache(id)
|
|
|
|
Rails.cache.delete(account_lookup_cache_key(id)) if id
|
2014-05-03 00:35:29 +08:00
|
|
|
rescue
|
2011-02-01 09:57:29 +08:00
|
|
|
nil
|
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2015-07-01 23:31:41 +08:00
|
|
|
def setup_cache_invalidation
|
|
|
|
@invalidations = []
|
2015-01-15 04:59:43 +08:00
|
|
|
unless self.new_record?
|
2015-07-01 23:31:41 +08:00
|
|
|
@invalidations += ['default_storage_quota', 'current_quota'] if self.try_rescue(:default_storage_quota_changed?)
|
|
|
|
@invalidations << 'default_group_storage_quota' if self.try_rescue(:default_group_storage_quota_changed?)
|
2015-01-15 04:59:43 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-07-01 23:31:41 +08:00
|
|
|
def invalidate_caches_if_changed
|
|
|
|
@invalidations ||= []
|
|
|
|
@invalidations += Account.inheritable_settings if @invalidate_settings_cache
|
|
|
|
if @invalidations.present?
|
|
|
|
@invalidations.each do |key|
|
|
|
|
Rails.cache.delete([key, self.global_id].cache_key)
|
|
|
|
end
|
|
|
|
Account.send_later_if_production(:invalidate_inherited_caches, self, @invalidations)
|
|
|
|
end
|
2015-01-15 04:59:43 +08:00
|
|
|
end
|
|
|
|
|
2015-07-01 23:31:41 +08:00
|
|
|
def self.invalidate_inherited_caches(parent_account, keys)
|
|
|
|
parent_account.shard.activate do
|
|
|
|
account_ids = Account.sub_account_ids_recursive(parent_account.id).map{|id| Shard.global_id_for(id)}
|
2015-01-15 04:59:43 +08:00
|
|
|
account_ids.each do |id|
|
2015-07-01 23:31:41 +08:00
|
|
|
keys.each do |key|
|
|
|
|
Rails.cache.delete([key, id].cache_key)
|
|
|
|
end
|
2015-01-15 04:59:43 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def quota
|
2015-07-01 23:31:41 +08:00
|
|
|
Rails.cache.fetch(['current_quota', self.global_id].cache_key) do
|
2011-06-18 00:46:48 +08:00
|
|
|
read_attribute(:storage_quota) ||
|
|
|
|
(self.parent_account.default_storage_quota rescue nil) ||
|
2013-10-05 04:02:49 +08:00
|
|
|
Setting.get('account_default_quota', 500.megabytes.to_s).to_i
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def default_storage_quota
|
2015-07-01 23:31:41 +08:00
|
|
|
Rails.cache.fetch(['default_storage_quota', self.global_id].cache_key) do
|
2014-09-10 06:04:53 +08:00
|
|
|
read_attribute(:default_storage_quota) ||
|
|
|
|
(self.parent_account.default_storage_quota rescue nil) ||
|
|
|
|
Setting.get('account_default_quota', 500.megabytes.to_s).to_i
|
|
|
|
end
|
2011-06-18 00:46:48 +08:00
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2011-06-18 00:46:48 +08:00
|
|
|
def default_storage_quota_mb
|
2011-06-18 04:19:46 +08:00
|
|
|
default_storage_quota / 1.megabyte
|
2011-06-18 00:46:48 +08:00
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2011-06-18 00:46:48 +08:00
|
|
|
def default_storage_quota_mb=(val)
|
2011-07-09 21:22:33 +08:00
|
|
|
self.default_storage_quota = val.try(:to_i).try(:megabytes)
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def default_storage_quota=(val)
|
|
|
|
val = val.to_f
|
|
|
|
val = nil if val <= 0
|
|
|
|
# If the value is the same as the inherited value, then go
|
|
|
|
# ahead and blank it so it keeps using the inherited value
|
|
|
|
if parent_account && parent_account.default_storage_quota == val
|
|
|
|
val = nil
|
|
|
|
end
|
|
|
|
write_attribute(:default_storage_quota, val)
|
|
|
|
end
|
2012-07-03 03:16:08 +08:00
|
|
|
|
|
|
|
def default_user_storage_quota
|
|
|
|
read_attribute(:default_user_storage_quota) ||
|
|
|
|
User.default_storage_quota
|
|
|
|
end
|
|
|
|
|
|
|
|
def default_user_storage_quota=(val)
|
|
|
|
val = val.to_i
|
|
|
|
val = nil if val == User.default_storage_quota || val <= 0
|
|
|
|
write_attribute(:default_user_storage_quota, val)
|
|
|
|
end
|
|
|
|
|
|
|
|
def default_user_storage_quota_mb
|
|
|
|
default_user_storage_quota / 1.megabyte
|
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2012-07-03 03:16:08 +08:00
|
|
|
def default_user_storage_quota_mb=(val)
|
|
|
|
self.default_user_storage_quota = val.try(:to_i).try(:megabytes)
|
|
|
|
end
|
|
|
|
|
2013-06-05 02:50:15 +08:00
|
|
|
def default_group_storage_quota
|
2015-07-01 23:31:41 +08:00
|
|
|
Rails.cache.fetch(['default_group_storage_quota', self.global_id].cache_key) do
|
2014-11-20 01:31:19 +08:00
|
|
|
read_attribute(:default_group_storage_quota) ||
|
|
|
|
(self.parent_account.default_group_storage_quota rescue nil) ||
|
2013-06-05 02:50:15 +08:00
|
|
|
Group.default_storage_quota
|
2014-11-20 01:31:19 +08:00
|
|
|
end
|
2013-06-05 02:50:15 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def default_group_storage_quota=(val)
|
|
|
|
val = val.to_i
|
2014-11-20 01:31:19 +08:00
|
|
|
if (val == Group.default_storage_quota) || (val <= 0) ||
|
|
|
|
(self.parent_account && self.parent_account.default_group_storage_quota == val)
|
|
|
|
val = nil
|
|
|
|
end
|
2013-06-05 02:50:15 +08:00
|
|
|
write_attribute(:default_group_storage_quota, val)
|
|
|
|
end
|
|
|
|
|
|
|
|
def default_group_storage_quota_mb
|
|
|
|
default_group_storage_quota / 1.megabyte
|
|
|
|
end
|
|
|
|
|
|
|
|
def default_group_storage_quota_mb=(val)
|
|
|
|
self.default_group_storage_quota = val.try(:to_i).try(:megabytes)
|
|
|
|
end
|
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def turnitin_shared_secret=(secret)
|
|
|
|
return if secret.blank?
|
|
|
|
self.turnitin_crypted_secret, self.turnitin_salt = Canvas::Security.encrypt_password(secret, 'instructure_turnitin_secret_shared')
|
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def turnitin_shared_secret
|
|
|
|
return nil unless self.turnitin_salt && self.turnitin_crypted_secret
|
|
|
|
Canvas::Security.decrypt_password(self.turnitin_crypted_secret, self.turnitin_salt, 'instructure_turnitin_secret_shared')
|
|
|
|
end
|
2012-08-15 00:19:31 +08:00
|
|
|
|
2014-09-11 00:06:39 +08:00
|
|
|
def self.account_chain(starting_account_id)
|
|
|
|
return [] unless starting_account_id
|
|
|
|
|
|
|
|
if ActiveRecord::Base.configurations[Rails.env]['adapter'] == 'postgresql'
|
|
|
|
chain = Shard.shard_for(starting_account_id).activate do
|
|
|
|
Account.find_by_sql(<<-SQL)
|
2013-12-04 07:00:14 +08:00
|
|
|
WITH RECURSIVE t AS (
|
2014-09-11 00:06:39 +08:00
|
|
|
SELECT * FROM accounts WHERE id=#{Shard.local_id_for(starting_account_id).first}
|
2013-12-04 07:00:14 +08:00
|
|
|
UNION
|
|
|
|
SELECT accounts.* FROM accounts INNER JOIN t ON accounts.id=t.parent_account_id
|
|
|
|
)
|
|
|
|
SELECT * FROM t
|
2014-09-11 00:06:39 +08:00
|
|
|
SQL
|
|
|
|
end
|
|
|
|
else
|
|
|
|
account = Account.find(starting_account_id)
|
|
|
|
chain = [account]
|
|
|
|
while account.parent_account
|
|
|
|
account = account.parent_account
|
|
|
|
chain << account
|
2012-08-07 00:20:48 +08:00
|
|
|
end
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
2014-09-11 00:06:39 +08:00
|
|
|
chain
|
|
|
|
end
|
|
|
|
|
|
|
|
def account_chain(opts = {})
|
|
|
|
@account_chain ||= [self] + Account.account_chain(self.parent_account_id)
|
2013-12-04 07:00:14 +08:00
|
|
|
results = @account_chain.dup
|
2014-09-11 00:06:39 +08:00
|
|
|
results << self.root_account if !results.map(&:id).include?(self.root_account_id) && !root_account?
|
2013-12-04 07:00:14 +08:00
|
|
|
results << Account.site_admin if opts[:include_site_admin] && !self.site_admin?
|
|
|
|
results
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
2012-01-18 04:35:11 +08:00
|
|
|
|
2012-08-07 00:20:48 +08:00
|
|
|
def account_chain_loop
|
|
|
|
# this record hasn't been saved to the db yet, so if the the chain includes
|
|
|
|
# this account, it won't point to the new parent yet, and should still be
|
|
|
|
# valid
|
2014-09-11 00:06:39 +08:00
|
|
|
if self.parent_account.account_chain.include?(self)
|
2012-08-07 00:20:48 +08:00
|
|
|
errors.add(:parent_account_id,
|
|
|
|
"Setting account #{self.sis_source_id || self.id}'s parent to #{self.parent_account.sis_source_id || self.parent_account_id} would create a loop")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-08-15 00:19:31 +08:00
|
|
|
# returns all sub_accounts recursively as far down as they go, in id order
|
|
|
|
# because this uses a custom sql query for postgresql, we can't use a normal
|
|
|
|
# named scope, so we pass the limit and offset into the method instead and
|
|
|
|
# build our own query string
|
|
|
|
def sub_accounts_recursive(limit, offset)
|
2013-03-08 08:08:47 +08:00
|
|
|
if ActiveRecord::Base.configurations[Rails.env]['adapter'] == 'postgresql'
|
2012-08-15 00:19:31 +08:00
|
|
|
Account.find_by_sql([<<-SQL, self.id, limit.to_i, offset.to_i])
|
|
|
|
WITH RECURSIVE t AS (
|
2013-06-06 02:08:00 +08:00
|
|
|
SELECT * FROM accounts
|
|
|
|
WHERE parent_account_id = ? AND workflow_state <>'deleted'
|
2012-08-15 00:19:31 +08:00
|
|
|
UNION
|
2013-06-06 02:08:00 +08:00
|
|
|
SELECT accounts.* FROM accounts
|
|
|
|
INNER JOIN t ON accounts.parent_account_id = t.id
|
|
|
|
WHERE accounts.workflow_state <>'deleted'
|
2012-08-15 00:19:31 +08:00
|
|
|
)
|
|
|
|
SELECT * FROM t ORDER BY parent_account_id, id LIMIT ? OFFSET ?
|
|
|
|
SQL
|
|
|
|
else
|
|
|
|
account_descendents = lambda do |id|
|
2013-06-06 02:08:00 +08:00
|
|
|
as = Account.where(:parent_account_id => id).active.order(:id)
|
2012-08-15 00:19:31 +08:00
|
|
|
as.empty? ?
|
|
|
|
[] :
|
|
|
|
as << as.map { |a| account_descendents.call(a.id) }
|
|
|
|
end
|
|
|
|
account_descendents.call(id).flatten[offset, limit]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-09-08 20:48:45 +08:00
|
|
|
def self.sub_account_ids_recursive(parent_account_id)
|
|
|
|
if connection.adapter_name == 'PostgreSQL'
|
|
|
|
sql = "
|
|
|
|
WITH RECURSIVE t AS (
|
|
|
|
SELECT id, parent_account_id FROM accounts
|
|
|
|
WHERE parent_account_id = #{parent_account_id} AND workflow_state <> 'deleted'
|
|
|
|
UNION
|
|
|
|
SELECT accounts.id, accounts.parent_account_id FROM accounts
|
|
|
|
INNER JOIN t ON accounts.parent_account_id = t.id
|
|
|
|
WHERE accounts.workflow_state <> 'deleted'
|
|
|
|
)
|
|
|
|
SELECT id FROM t"
|
|
|
|
Account.find_by_sql(sql).map(&:id)
|
|
|
|
else
|
|
|
|
account_descendants = lambda do |ids|
|
|
|
|
as = Account.where(:parent_account_id => ids).active.pluck(:id)
|
|
|
|
as + account_descendants.call(as)
|
|
|
|
end
|
|
|
|
account_descendants.call([parent_account_id])
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-01-18 04:35:11 +08:00
|
|
|
def associated_accounts
|
|
|
|
self.account_chain
|
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def membership_for_user(user)
|
2014-09-12 03:44:34 +08:00
|
|
|
self.account_users.where(user_id: user).first if user
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
2012-03-10 01:30:31 +08:00
|
|
|
|
2014-09-08 20:48:45 +08:00
|
|
|
def available_custom_account_roles(include_inactive=false)
|
2015-03-19 21:51:43 +08:00
|
|
|
available_custom_roles(include_inactive).for_accounts
|
2012-12-27 04:09:58 +08:00
|
|
|
end
|
|
|
|
|
2014-09-08 20:48:45 +08:00
|
|
|
def available_account_roles(include_inactive=false, user = nil)
|
|
|
|
account_roles = available_custom_account_roles(include_inactive)
|
|
|
|
account_roles << Role.get_built_in_role('AccountAdmin')
|
2014-05-31 02:01:49 +08:00
|
|
|
if user
|
2014-09-08 20:48:45 +08:00
|
|
|
account_roles.select! { |role| au = account_users.new; au.role_id = role.id; au.grants_right?(user, :create) }
|
2014-05-31 02:01:49 +08:00
|
|
|
end
|
2012-11-30 05:23:30 +08:00
|
|
|
account_roles
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
2011-08-13 00:12:13 +08:00
|
|
|
|
2014-09-08 20:48:45 +08:00
|
|
|
def available_custom_course_roles(include_inactive=false)
|
2015-03-19 21:51:43 +08:00
|
|
|
available_custom_roles(include_inactive).for_courses
|
2014-09-08 20:48:45 +08:00
|
|
|
end
|
|
|
|
|
2012-12-13 23:24:21 +08:00
|
|
|
def available_course_roles(include_inactive=false)
|
2014-09-08 20:48:45 +08:00
|
|
|
course_roles = available_custom_course_roles(include_inactive)
|
|
|
|
course_roles += Role.built_in_course_roles
|
|
|
|
course_roles
|
2012-11-27 03:27:24 +08:00
|
|
|
end
|
|
|
|
|
2015-03-19 21:51:43 +08:00
|
|
|
def available_custom_roles(include_inactive=false)
|
|
|
|
@role_chain_ids ||= self.account_chain.map(&:id)
|
|
|
|
scope = Role.where(:account_id => @role_chain_ids)
|
|
|
|
scope = include_inactive ? scope.not_deleted : scope.active
|
|
|
|
scope
|
|
|
|
end
|
|
|
|
|
2014-09-08 20:48:45 +08:00
|
|
|
def available_roles(include_inactive=false)
|
|
|
|
available_account_roles(include_inactive) + available_course_roles(include_inactive)
|
2012-12-08 03:28:30 +08:00
|
|
|
end
|
|
|
|
|
2014-09-08 20:48:45 +08:00
|
|
|
def get_account_role_by_name(role_name)
|
|
|
|
role = get_role_by_name(role_name)
|
|
|
|
return role if role && role.account_role?
|
|
|
|
end
|
|
|
|
|
|
|
|
def get_course_role_by_name(role_name)
|
|
|
|
role = get_role_by_name(role_name)
|
|
|
|
return role if role && role.course_role?
|
|
|
|
end
|
|
|
|
|
|
|
|
def get_role_by_name(role_name)
|
|
|
|
if role = Role.get_built_in_role(role_name)
|
|
|
|
return role
|
|
|
|
end
|
|
|
|
|
|
|
|
self.shard.activate do
|
|
|
|
role_scope = Role.not_deleted.where(:name => role_name)
|
|
|
|
if connection.adapter_name == 'PostgreSQL'
|
|
|
|
role_scope = role_scope.where("account_id = ? OR
|
|
|
|
account_id IN (
|
|
|
|
WITH RECURSIVE t AS (
|
|
|
|
SELECT id, parent_account_id FROM accounts WHERE id = ?
|
|
|
|
UNION
|
|
|
|
SELECT accounts.id, accounts.parent_account_id FROM accounts INNER JOIN t ON accounts.id=t.parent_account_id
|
|
|
|
)
|
|
|
|
SELECT id FROM t
|
|
|
|
)", self.id, self.id)
|
|
|
|
else
|
|
|
|
role_scope = role_scope.where(:account_id => self.account_chain.map(&:id))
|
|
|
|
end
|
|
|
|
|
|
|
|
role_scope.first
|
|
|
|
end
|
2012-11-27 03:27:24 +08:00
|
|
|
end
|
|
|
|
|
2014-09-08 20:48:45 +08:00
|
|
|
def get_role_by_id(role_id)
|
|
|
|
role = Role.get_role_by_id(role_id)
|
|
|
|
return role if valid_role?(role)
|
2012-11-30 05:23:30 +08:00
|
|
|
end
|
|
|
|
|
2014-09-08 20:48:45 +08:00
|
|
|
def valid_role?(role)
|
|
|
|
role && (role.built_in? || (self.id == role.account_id) || self.account_chain.map(&:id).include?(role.account_id))
|
2012-11-27 03:27:24 +08:00
|
|
|
end
|
|
|
|
|
2011-07-09 00:36:32 +08:00
|
|
|
def login_handle_name_is_customized?
|
refactor views for unified AAC administration
closes CNVS-20076
First, pull a presenter out of the AAC index
This thing needs more flexibility before introducing
multiple OAuth connectors. This commit adds a couple characterization
specs for the AAC controller, then drives out a presenter to pull
as much logic and config out of the nested views as possible.
Then, this commit refactors the
previously-somewhat-bespoke-and-presumptive sac
configuration into a workflow that shows each aac
in turn according to it's type, creating forms for
each type at the bottom, and
showing the relevant form for a new one based on
selection on the right.
Have regression tested in the browser to the level
of CRUD functionality, but also deserves solid QA
for SSO functionality post-configuration.
DONE:
-successful CAS creation/editing/deletion
-successful LDAP creation/editing/deletion
-proper differentiation between LDAP primary and secondary
-proper SAML creation/editing
-move away from "update_all" deprecated endpoint
-Selenium Spec fixes
-ensure discovery URL and debugging workflows for saml
-remove duplication from views
-tear down old JS workflow
-apply appropriate tests for new behavior
-remove presenter methods that are no longer valuable
-Moved change_password_url and login_handle_name
-up to account settings, removed them from AACs, and built
-migrations to manage the transition.
-Found and fixed all references to change_password_url on AACs
-Found and fixes all references to login_handle_name on AACs
-add datafixup for migrating AAC data to account settings
-unify repetative individual files into single form delcarations \o/
-remove old SAML editing js
-Make sure SAML still works
-Make LDAP partial flow just like SAML/CAS
-Unify position information across all types
-update "acts_as_list" to support STI classes
-move discovery URL into account auth form
-remove discover URL js management
-Unify form generation between new/existing aacs
-deprecate discovery url API endpoints
-update docs for authorization settings to deprecate their usage in AAC
api and redirect their values to current settings for now
-make delete links non-js-y to stop this silly page refresh on api
completion
-make form submissions actually submit the form rather than do this silly
page refresh on api completion
-See if anything needs “Edit Details” button, remove if not
-Wire up removing account settings by blanking out form
-Removed "cancel" button from form because fields are always open
-placate gergich
-Test removing config info
-Test population fixup on real data
-write docs for authorization settings
-fix existing specs
-fix routing and docs to not break doc generation
-fix stupid jenkins task that thinks it can’t see controls
-re-fix selenium
-fix saml debugging workflow
-write tests for acts_as_list behavior
-write tests for authorization settings
-remove auth_info types of things
-clean up and unify styles where possible
TEST PLAN:
Regression test creating/deleting/editing and logging
in with SSO solutions for CAS, LDAP, and SAML.
Should be no functional behavior modification, though
workflow will be a little more unified between the
3 currently supported types (each one will
require using the menu in the right sidebar
to add a new AAC).
Also test setting and deleting account settings through
the form underneath the configs when there are AACs in existence.
Finally, make sure that the SAML Debugging workflow still works.
Change-Id: I448db10185512d1b9469c2a425be0a3bcf9e6ebf
Reviewed-on: https://gerrit.instructure.com/53448
Tested-by: Jenkins
Reviewed-by: Cody Cutrer <cody@instructure.com>
QA-Review: August Thornton <august@instructure.com>
Product-Review: Ethan Vizitei <evizitei@instructure.com>
2015-04-28 05:23:04 +08:00
|
|
|
self.login_handle_name.present?
|
2011-07-09 00:36:32 +08:00
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
refactor views for unified AAC administration
closes CNVS-20076
First, pull a presenter out of the AAC index
This thing needs more flexibility before introducing
multiple OAuth connectors. This commit adds a couple characterization
specs for the AAC controller, then drives out a presenter to pull
as much logic and config out of the nested views as possible.
Then, this commit refactors the
previously-somewhat-bespoke-and-presumptive sac
configuration into a workflow that shows each aac
in turn according to it's type, creating forms for
each type at the bottom, and
showing the relevant form for a new one based on
selection on the right.
Have regression tested in the browser to the level
of CRUD functionality, but also deserves solid QA
for SSO functionality post-configuration.
DONE:
-successful CAS creation/editing/deletion
-successful LDAP creation/editing/deletion
-proper differentiation between LDAP primary and secondary
-proper SAML creation/editing
-move away from "update_all" deprecated endpoint
-Selenium Spec fixes
-ensure discovery URL and debugging workflows for saml
-remove duplication from views
-tear down old JS workflow
-apply appropriate tests for new behavior
-remove presenter methods that are no longer valuable
-Moved change_password_url and login_handle_name
-up to account settings, removed them from AACs, and built
-migrations to manage the transition.
-Found and fixed all references to change_password_url on AACs
-Found and fixes all references to login_handle_name on AACs
-add datafixup for migrating AAC data to account settings
-unify repetative individual files into single form delcarations \o/
-remove old SAML editing js
-Make sure SAML still works
-Make LDAP partial flow just like SAML/CAS
-Unify position information across all types
-update "acts_as_list" to support STI classes
-move discovery URL into account auth form
-remove discover URL js management
-Unify form generation between new/existing aacs
-deprecate discovery url API endpoints
-update docs for authorization settings to deprecate their usage in AAC
api and redirect their values to current settings for now
-make delete links non-js-y to stop this silly page refresh on api
completion
-make form submissions actually submit the form rather than do this silly
page refresh on api completion
-See if anything needs “Edit Details” button, remove if not
-Wire up removing account settings by blanking out form
-Removed "cancel" button from form because fields are always open
-placate gergich
-Test removing config info
-Test population fixup on real data
-write docs for authorization settings
-fix existing specs
-fix routing and docs to not break doc generation
-fix stupid jenkins task that thinks it can’t see controls
-re-fix selenium
-fix saml debugging workflow
-write tests for acts_as_list behavior
-write tests for authorization settings
-remove auth_info types of things
-clean up and unify styles where possible
TEST PLAN:
Regression test creating/deleting/editing and logging
in with SSO solutions for CAS, LDAP, and SAML.
Should be no functional behavior modification, though
workflow will be a little more unified between the
3 currently supported types (each one will
require using the menu in the right sidebar
to add a new AAC).
Also test setting and deleting account settings through
the form underneath the configs when there are AACs in existence.
Finally, make sure that the SAML Debugging workflow still works.
Change-Id: I448db10185512d1b9469c2a425be0a3bcf9e6ebf
Reviewed-on: https://gerrit.instructure.com/53448
Tested-by: Jenkins
Reviewed-by: Cody Cutrer <cody@instructure.com>
QA-Review: August Thornton <august@instructure.com>
Product-Review: Ethan Vizitei <evizitei@instructure.com>
2015-04-28 05:23:04 +08:00
|
|
|
def login_handle_name_with_inference
|
2014-08-21 02:00:32 +08:00
|
|
|
if login_handle_name_is_customized?
|
refactor views for unified AAC administration
closes CNVS-20076
First, pull a presenter out of the AAC index
This thing needs more flexibility before introducing
multiple OAuth connectors. This commit adds a couple characterization
specs for the AAC controller, then drives out a presenter to pull
as much logic and config out of the nested views as possible.
Then, this commit refactors the
previously-somewhat-bespoke-and-presumptive sac
configuration into a workflow that shows each aac
in turn according to it's type, creating forms for
each type at the bottom, and
showing the relevant form for a new one based on
selection on the right.
Have regression tested in the browser to the level
of CRUD functionality, but also deserves solid QA
for SSO functionality post-configuration.
DONE:
-successful CAS creation/editing/deletion
-successful LDAP creation/editing/deletion
-proper differentiation between LDAP primary and secondary
-proper SAML creation/editing
-move away from "update_all" deprecated endpoint
-Selenium Spec fixes
-ensure discovery URL and debugging workflows for saml
-remove duplication from views
-tear down old JS workflow
-apply appropriate tests for new behavior
-remove presenter methods that are no longer valuable
-Moved change_password_url and login_handle_name
-up to account settings, removed them from AACs, and built
-migrations to manage the transition.
-Found and fixed all references to change_password_url on AACs
-Found and fixes all references to login_handle_name on AACs
-add datafixup for migrating AAC data to account settings
-unify repetative individual files into single form delcarations \o/
-remove old SAML editing js
-Make sure SAML still works
-Make LDAP partial flow just like SAML/CAS
-Unify position information across all types
-update "acts_as_list" to support STI classes
-move discovery URL into account auth form
-remove discover URL js management
-Unify form generation between new/existing aacs
-deprecate discovery url API endpoints
-update docs for authorization settings to deprecate their usage in AAC
api and redirect their values to current settings for now
-make delete links non-js-y to stop this silly page refresh on api
completion
-make form submissions actually submit the form rather than do this silly
page refresh on api completion
-See if anything needs “Edit Details” button, remove if not
-Wire up removing account settings by blanking out form
-Removed "cancel" button from form because fields are always open
-placate gergich
-Test removing config info
-Test population fixup on real data
-write docs for authorization settings
-fix existing specs
-fix routing and docs to not break doc generation
-fix stupid jenkins task that thinks it can’t see controls
-re-fix selenium
-fix saml debugging workflow
-write tests for acts_as_list behavior
-write tests for authorization settings
-remove auth_info types of things
-clean up and unify styles where possible
TEST PLAN:
Regression test creating/deleting/editing and logging
in with SSO solutions for CAS, LDAP, and SAML.
Should be no functional behavior modification, though
workflow will be a little more unified between the
3 currently supported types (each one will
require using the menu in the right sidebar
to add a new AAC).
Also test setting and deleting account settings through
the form underneath the configs when there are AACs in existence.
Finally, make sure that the SAML Debugging workflow still works.
Change-Id: I448db10185512d1b9469c2a425be0a3bcf9e6ebf
Reviewed-on: https://gerrit.instructure.com/53448
Tested-by: Jenkins
Reviewed-by: Cody Cutrer <cody@instructure.com>
QA-Review: August Thornton <august@instructure.com>
Product-Review: Ethan Vizitei <evizitei@instructure.com>
2015-04-28 05:23:04 +08:00
|
|
|
self.login_handle_name
|
2014-08-21 02:00:32 +08:00
|
|
|
elsif self.delegated_authentication?
|
|
|
|
AccountAuthorizationConfig.default_delegated_login_handle_name
|
|
|
|
else
|
|
|
|
AccountAuthorizationConfig.default_login_handle_name
|
|
|
|
end
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def self_and_all_sub_accounts
|
2013-03-19 03:07:47 +08:00
|
|
|
@self_and_all_sub_accounts ||= Account.where("root_account_id=? OR parent_account_id=?", self, self).pluck(:id).uniq + [self.id]
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
workflow do
|
|
|
|
state :active
|
|
|
|
state :deleted
|
|
|
|
end
|
2011-08-13 00:12:13 +08:00
|
|
|
|
|
|
|
def account_users_for(user)
|
2012-09-18 03:22:39 +08:00
|
|
|
return [] unless user
|
2011-08-13 00:12:13 +08:00
|
|
|
@account_users_cache ||= {}
|
2012-09-18 03:22:39 +08:00
|
|
|
if self == Account.site_admin
|
2013-12-18 00:56:57 +08:00
|
|
|
shard.activate do
|
2014-08-29 00:33:59 +08:00
|
|
|
@account_users_cache[user.global_id] ||= begin
|
2014-09-08 20:48:45 +08:00
|
|
|
all_site_admin_account_users_hash = MultiCache.fetch("all_site_admin_account_users3") do
|
2014-08-29 00:33:59 +08:00
|
|
|
# this is a plain ruby hash to keep the cached portion as small as possible
|
2014-09-08 20:48:45 +08:00
|
|
|
self.account_users.inject({}) { |result, au| result[au.user_id] ||= []; result[au.user_id] << [au.id, au.role_id]; result }
|
2014-08-29 00:33:59 +08:00
|
|
|
end
|
2014-09-08 20:48:45 +08:00
|
|
|
(all_site_admin_account_users_hash[user.id] || []).map do |(id, role_id)|
|
2014-08-29 00:33:59 +08:00
|
|
|
au = AccountUser.new
|
|
|
|
au.id = id
|
|
|
|
au.account = Account.site_admin
|
|
|
|
au.user = user
|
2014-09-08 20:48:45 +08:00
|
|
|
au.role_id = role_id
|
2014-08-29 00:33:59 +08:00
|
|
|
au.readonly!
|
|
|
|
au
|
|
|
|
end
|
|
|
|
end
|
2013-12-18 00:56:57 +08:00
|
|
|
end
|
2012-09-18 03:22:39 +08:00
|
|
|
else
|
|
|
|
@account_chain_ids ||= self.account_chain(:include_site_admin => true).map { |a| a.active? ? a.id : nil }.compact
|
2014-01-31 00:04:56 +08:00
|
|
|
@account_users_cache[user.global_id] ||= Shard.partition_by_shard(@account_chain_ids) do |account_chain_ids|
|
2012-09-18 03:22:39 +08:00
|
|
|
if account_chain_ids == [Account.site_admin.id]
|
|
|
|
Account.site_admin.account_users_for(user)
|
|
|
|
else
|
2013-03-19 03:07:47 +08:00
|
|
|
AccountUser.where(:account_id => account_chain_ids, :user_id => user).all
|
2012-09-18 03:22:39 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2014-01-31 00:04:56 +08:00
|
|
|
@account_users_cache[user.global_id] ||= []
|
|
|
|
@account_users_cache[user.global_id]
|
2011-08-13 00:12:13 +08:00
|
|
|
end
|
2012-02-21 06:57:32 +08:00
|
|
|
|
|
|
|
# returns all account users for this entire account tree
|
|
|
|
def all_account_users_for(user)
|
|
|
|
raise "must be a root account" unless self.root_account?
|
|
|
|
Shard.partition_by_shard([self, Account.site_admin].uniq) do |accounts|
|
2014-10-10 05:58:29 +08:00
|
|
|
next unless user.associated_shards.include?(Shard.current)
|
2013-03-19 03:07:47 +08:00
|
|
|
AccountUser.includes(:account).joins(:account).where("user_id=? AND (root_account_id IN (?) OR account_id IN (?))", user, accounts, accounts)
|
2012-02-21 06:57:32 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
set_policy do
|
2014-11-05 00:51:02 +08:00
|
|
|
enrollment_types = RoleOverride.enrollment_type_labels.map { |role| role[:name] }
|
2012-02-28 23:50:10 +08:00
|
|
|
RoleOverride.permissions.each do |permission, details|
|
2012-12-07 07:15:53 +08:00
|
|
|
given { |user| self.account_users_for(user).any? { |au| au.has_permission_to?(self, permission) && (!details[:if] || send(details[:if])) } }
|
2011-07-14 00:24:17 +08:00
|
|
|
can permission
|
2012-03-31 03:40:12 +08:00
|
|
|
can :create_courses if permission == :manage_courses
|
2012-02-28 23:50:10 +08:00
|
|
|
|
|
|
|
next unless details[:account_only]
|
2014-09-08 20:48:45 +08:00
|
|
|
((details[:available_to] | details[:true_for]) & enrollment_types).each do |role_name|
|
|
|
|
given { |user|
|
|
|
|
user && RoleOverride.permission_for(self, permission, Role.get_built_in_role(role_name))[:enabled] &&
|
2015-07-16 05:14:09 +08:00
|
|
|
self.course_account_associations.joins("INNER JOIN #{Enrollment.quoted_table_name} ON course_account_associations.course_id=enrollments.course_id").
|
2014-09-08 20:48:45 +08:00
|
|
|
where("enrollments.type=? AND enrollments.workflow_state IN ('active', 'completed') AND user_id=?", role_name, user).first &&
|
2012-08-17 06:41:43 +08:00
|
|
|
(!details[:if] || send(details[:if])) }
|
2012-02-28 23:50:10 +08:00
|
|
|
can permission
|
|
|
|
end
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
|
|
|
|
2011-08-13 00:12:13 +08:00
|
|
|
given { |user| !self.account_users_for(user).empty? }
|
2012-11-01 04:28:33 +08:00
|
|
|
can :read and can :manage and can :update and can :delete and can :read_outcomes
|
2011-08-10 06:09:17 +08:00
|
|
|
|
|
|
|
given { |user|
|
|
|
|
result = false
|
|
|
|
|
2013-03-19 03:07:47 +08:00
|
|
|
if !site_admin? && user
|
2014-02-28 04:08:29 +08:00
|
|
|
scope = root_account.enrollments.active.where(user_id: user)
|
2013-03-19 03:07:47 +08:00
|
|
|
result = root_account.teachers_can_create_courses? &&
|
|
|
|
scope.where(:type => ['TeacherEnrollment', 'DesignerEnrollment']).exists?
|
|
|
|
result ||= root_account.students_can_create_courses? &&
|
|
|
|
scope.where(:type => ['StudentEnrollment', 'ObserverEnrollment']).exists?
|
|
|
|
result ||= root_account.no_enrollments_can_create_courses? &&
|
2013-04-23 13:13:07 +08:00
|
|
|
!scope.exists?
|
2011-08-10 06:09:17 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
result
|
|
|
|
}
|
|
|
|
can :create_courses
|
2012-11-01 04:28:33 +08:00
|
|
|
|
|
|
|
# any logged in user can read global outcomes, but must be checked against the site admin
|
2014-06-12 00:44:11 +08:00
|
|
|
given{ |user| self.site_admin? && user }
|
2012-11-01 04:28:33 +08:00
|
|
|
can :read_global_outcomes
|
|
|
|
|
|
|
|
# any user with an association to this account can read the outcomes in the account
|
2014-09-12 03:44:34 +08:00
|
|
|
given{ |user| user && self.user_account_associations.where(user_id: user).exists? }
|
2012-11-01 04:28:33 +08:00
|
|
|
can :read_outcomes
|
2014-09-28 01:10:48 +08:00
|
|
|
|
|
|
|
# any user with an admin enrollment in one of the courses can read
|
|
|
|
given { |user| user && self.courses.where(:id => user.enrollments.admin.pluck(:course_id)).exists? }
|
|
|
|
can :read
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
alias_method :destroy!, :destroy
|
|
|
|
def destroy
|
|
|
|
self.workflow_state = 'deleted'
|
2013-10-30 05:34:26 +08:00
|
|
|
self.deleted_at = Time.now.utc
|
2011-02-01 09:57:29 +08:00
|
|
|
save!
|
|
|
|
end
|
2012-04-11 03:54:42 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def to_atom
|
|
|
|
Atom::Entry.new do |entry|
|
|
|
|
entry.title = self.name
|
|
|
|
entry.updated = self.updated_at
|
|
|
|
entry.published = self.created_at
|
2014-05-03 00:35:29 +08:00
|
|
|
entry.links << Atom::Link.new(:rel => 'alternate',
|
2011-02-01 09:57:29 +08:00
|
|
|
:href => "/accounts/#{self.id}")
|
|
|
|
end
|
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def default_enrollment_term
|
2011-05-07 02:22:03 +08:00
|
|
|
return @default_enrollment_term if @default_enrollment_term
|
2011-12-28 05:38:15 +08:00
|
|
|
if self.root_account?
|
2014-09-12 03:44:34 +08:00
|
|
|
@default_enrollment_term = self.enrollment_terms.active.where(name: EnrollmentTerm::DEFAULT_TERM_NAME).first_or_create
|
2011-02-08 07:11:00 +08:00
|
|
|
end
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def context_code
|
2013-03-08 08:08:47 +08:00
|
|
|
raise "DONT USE THIS, use .short_name instead" unless Rails.env.production?
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def short_name
|
|
|
|
name
|
|
|
|
end
|
|
|
|
|
2012-12-07 14:28:37 +08:00
|
|
|
# can be set/overridden by plugin to enforce email pseudonyms
|
|
|
|
attr_accessor :email_pseudonyms
|
2013-03-29 05:19:35 +08:00
|
|
|
|
|
|
|
def password_policy
|
|
|
|
Canvas::PasswordPolicy.default_policy.merge(settings[:password_policy] || {})
|
|
|
|
end
|
|
|
|
|
2011-04-08 07:01:32 +08:00
|
|
|
def delegated_authentication?
|
2015-06-05 00:42:53 +08:00
|
|
|
authentication_providers.active.first.is_a?(AccountAuthorizationConfig::Delegated)
|
2011-04-08 07:01:32 +08:00
|
|
|
end
|
2011-11-22 03:28:20 +08:00
|
|
|
|
2011-05-24 09:14:52 +08:00
|
|
|
def forgot_password_external_url
|
refactor views for unified AAC administration
closes CNVS-20076
First, pull a presenter out of the AAC index
This thing needs more flexibility before introducing
multiple OAuth connectors. This commit adds a couple characterization
specs for the AAC controller, then drives out a presenter to pull
as much logic and config out of the nested views as possible.
Then, this commit refactors the
previously-somewhat-bespoke-and-presumptive sac
configuration into a workflow that shows each aac
in turn according to it's type, creating forms for
each type at the bottom, and
showing the relevant form for a new one based on
selection on the right.
Have regression tested in the browser to the level
of CRUD functionality, but also deserves solid QA
for SSO functionality post-configuration.
DONE:
-successful CAS creation/editing/deletion
-successful LDAP creation/editing/deletion
-proper differentiation between LDAP primary and secondary
-proper SAML creation/editing
-move away from "update_all" deprecated endpoint
-Selenium Spec fixes
-ensure discovery URL and debugging workflows for saml
-remove duplication from views
-tear down old JS workflow
-apply appropriate tests for new behavior
-remove presenter methods that are no longer valuable
-Moved change_password_url and login_handle_name
-up to account settings, removed them from AACs, and built
-migrations to manage the transition.
-Found and fixed all references to change_password_url on AACs
-Found and fixes all references to login_handle_name on AACs
-add datafixup for migrating AAC data to account settings
-unify repetative individual files into single form delcarations \o/
-remove old SAML editing js
-Make sure SAML still works
-Make LDAP partial flow just like SAML/CAS
-Unify position information across all types
-update "acts_as_list" to support STI classes
-move discovery URL into account auth form
-remove discover URL js management
-Unify form generation between new/existing aacs
-deprecate discovery url API endpoints
-update docs for authorization settings to deprecate their usage in AAC
api and redirect their values to current settings for now
-make delete links non-js-y to stop this silly page refresh on api
completion
-make form submissions actually submit the form rather than do this silly
page refresh on api completion
-See if anything needs “Edit Details” button, remove if not
-Wire up removing account settings by blanking out form
-Removed "cancel" button from form because fields are always open
-placate gergich
-Test removing config info
-Test population fixup on real data
-write docs for authorization settings
-fix existing specs
-fix routing and docs to not break doc generation
-fix stupid jenkins task that thinks it can’t see controls
-re-fix selenium
-fix saml debugging workflow
-write tests for acts_as_list behavior
-write tests for authorization settings
-remove auth_info types of things
-clean up and unify styles where possible
TEST PLAN:
Regression test creating/deleting/editing and logging
in with SSO solutions for CAS, LDAP, and SAML.
Should be no functional behavior modification, though
workflow will be a little more unified between the
3 currently supported types (each one will
require using the menu in the right sidebar
to add a new AAC).
Also test setting and deleting account settings through
the form underneath the configs when there are AACs in existence.
Finally, make sure that the SAML Debugging workflow still works.
Change-Id: I448db10185512d1b9469c2a425be0a3bcf9e6ebf
Reviewed-on: https://gerrit.instructure.com/53448
Tested-by: Jenkins
Reviewed-by: Cody Cutrer <cody@instructure.com>
QA-Review: August Thornton <august@instructure.com>
Product-Review: Ethan Vizitei <evizitei@instructure.com>
2015-04-28 05:23:04 +08:00
|
|
|
self.change_password_url
|
2011-05-24 09:14:52 +08:00
|
|
|
end
|
2011-04-08 07:01:32 +08:00
|
|
|
|
2012-09-29 06:02:02 +08:00
|
|
|
def multi_auth?
|
2015-06-05 00:42:53 +08:00
|
|
|
self.authentication_providers.active.count > 1
|
2012-09-29 06:02:02 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def auth_discovery_url=(url)
|
|
|
|
self.settings[:auth_discovery_url] = url
|
|
|
|
end
|
|
|
|
|
|
|
|
def auth_discovery_url
|
|
|
|
self.settings[:auth_discovery_url]
|
|
|
|
end
|
2012-10-11 02:58:09 +08:00
|
|
|
|
refactor views for unified AAC administration
closes CNVS-20076
First, pull a presenter out of the AAC index
This thing needs more flexibility before introducing
multiple OAuth connectors. This commit adds a couple characterization
specs for the AAC controller, then drives out a presenter to pull
as much logic and config out of the nested views as possible.
Then, this commit refactors the
previously-somewhat-bespoke-and-presumptive sac
configuration into a workflow that shows each aac
in turn according to it's type, creating forms for
each type at the bottom, and
showing the relevant form for a new one based on
selection on the right.
Have regression tested in the browser to the level
of CRUD functionality, but also deserves solid QA
for SSO functionality post-configuration.
DONE:
-successful CAS creation/editing/deletion
-successful LDAP creation/editing/deletion
-proper differentiation between LDAP primary and secondary
-proper SAML creation/editing
-move away from "update_all" deprecated endpoint
-Selenium Spec fixes
-ensure discovery URL and debugging workflows for saml
-remove duplication from views
-tear down old JS workflow
-apply appropriate tests for new behavior
-remove presenter methods that are no longer valuable
-Moved change_password_url and login_handle_name
-up to account settings, removed them from AACs, and built
-migrations to manage the transition.
-Found and fixed all references to change_password_url on AACs
-Found and fixes all references to login_handle_name on AACs
-add datafixup for migrating AAC data to account settings
-unify repetative individual files into single form delcarations \o/
-remove old SAML editing js
-Make sure SAML still works
-Make LDAP partial flow just like SAML/CAS
-Unify position information across all types
-update "acts_as_list" to support STI classes
-move discovery URL into account auth form
-remove discover URL js management
-Unify form generation between new/existing aacs
-deprecate discovery url API endpoints
-update docs for authorization settings to deprecate their usage in AAC
api and redirect their values to current settings for now
-make delete links non-js-y to stop this silly page refresh on api
completion
-make form submissions actually submit the form rather than do this silly
page refresh on api completion
-See if anything needs “Edit Details” button, remove if not
-Wire up removing account settings by blanking out form
-Removed "cancel" button from form because fields are always open
-placate gergich
-Test removing config info
-Test population fixup on real data
-write docs for authorization settings
-fix existing specs
-fix routing and docs to not break doc generation
-fix stupid jenkins task that thinks it can’t see controls
-re-fix selenium
-fix saml debugging workflow
-write tests for acts_as_list behavior
-write tests for authorization settings
-remove auth_info types of things
-clean up and unify styles where possible
TEST PLAN:
Regression test creating/deleting/editing and logging
in with SSO solutions for CAS, LDAP, and SAML.
Should be no functional behavior modification, though
workflow will be a little more unified between the
3 currently supported types (each one will
require using the menu in the right sidebar
to add a new AAC).
Also test setting and deleting account settings through
the form underneath the configs when there are AACs in existence.
Finally, make sure that the SAML Debugging workflow still works.
Change-Id: I448db10185512d1b9469c2a425be0a3bcf9e6ebf
Reviewed-on: https://gerrit.instructure.com/53448
Tested-by: Jenkins
Reviewed-by: Cody Cutrer <cody@instructure.com>
QA-Review: August Thornton <august@instructure.com>
Product-Review: Ethan Vizitei <evizitei@instructure.com>
2015-04-28 05:23:04 +08:00
|
|
|
def login_handle_name=(handle_name)
|
|
|
|
self.settings[:login_handle_name] = handle_name
|
|
|
|
end
|
|
|
|
|
|
|
|
def login_handle_name
|
|
|
|
self.settings[:login_handle_name]
|
|
|
|
end
|
|
|
|
|
|
|
|
def change_password_url=(change_password_url)
|
|
|
|
self.settings[:change_password_url] = change_password_url
|
|
|
|
end
|
|
|
|
|
|
|
|
def change_password_url
|
|
|
|
self.settings[:change_password_url]
|
|
|
|
end
|
|
|
|
|
2015-05-20 22:48:00 +08:00
|
|
|
def unknown_user_url=(unknown_user_url)
|
|
|
|
self.settings[:unknown_user_url] = unknown_user_url
|
|
|
|
end
|
|
|
|
|
|
|
|
def unknown_user_url
|
|
|
|
self.settings[:unknown_user_url]
|
|
|
|
end
|
|
|
|
|
2012-10-11 02:58:09 +08:00
|
|
|
def validate_auth_discovery_url
|
|
|
|
return if self.settings[:auth_discovery_url].blank?
|
|
|
|
|
|
|
|
begin
|
2014-03-20 23:52:26 +08:00
|
|
|
value, uri = CanvasHttp.validate_url(self.settings[:auth_discovery_url])
|
2012-10-11 02:58:09 +08:00
|
|
|
self.auth_discovery_url = value
|
|
|
|
rescue URI::InvalidURIError, ArgumentError
|
|
|
|
errors.add(:discovery_url, t('errors.invalid_discovery_url', "The discovery URL is not valid" ))
|
|
|
|
end
|
|
|
|
end
|
2012-11-28 05:37:32 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def find_courses(string)
|
|
|
|
self.all_courses.select{|c| c.name.match(string) }
|
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def find_users(string)
|
|
|
|
self.pseudonyms.map{|p| p.user }.select{|u| u.name.match(string) }
|
|
|
|
end
|
|
|
|
|
2014-08-26 22:53:26 +08:00
|
|
|
class << self
|
|
|
|
def special_accounts
|
|
|
|
@special_accounts ||= {}
|
|
|
|
end
|
|
|
|
|
|
|
|
def special_account_ids
|
|
|
|
@special_account_ids ||= {}
|
|
|
|
end
|
|
|
|
|
|
|
|
def special_account_timed_cache
|
|
|
|
@special_account_timed_cache ||= TimedCache.new(-> { Setting.get('account_special_account_cache_time', 60.seconds).to_i.ago }) do
|
|
|
|
special_accounts.clear
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def clear_special_account_cache!(force = false)
|
|
|
|
special_account_timed_cache.clear(force)
|
|
|
|
end
|
2014-11-05 01:50:55 +08:00
|
|
|
|
|
|
|
def define_special_account(key, name = nil)
|
|
|
|
name ||= key.to_s.titleize
|
|
|
|
instance_eval <<-RUBY
|
|
|
|
def self.#{key}(force_create = false)
|
|
|
|
get_special_account(:#{key}, #{name.inspect}, force_create)
|
|
|
|
end
|
|
|
|
RUBY
|
|
|
|
end
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
2014-11-05 01:50:55 +08:00
|
|
|
define_special_account(:default, 'Default Account')
|
|
|
|
define_special_account(:site_admin)
|
2011-02-01 09:57:29 +08:00
|
|
|
|
2012-09-05 05:45:14 +08:00
|
|
|
# an opportunity for plugins to load some other stuff up before caching the account
|
|
|
|
def precache
|
|
|
|
end
|
2011-02-01 09:57:29 +08:00
|
|
|
|
2012-09-05 05:45:14 +08:00
|
|
|
def self.find_cached(id)
|
2013-10-05 04:25:07 +08:00
|
|
|
account = Rails.cache.fetch(account_lookup_cache_key(id)) do
|
2014-09-12 03:44:34 +08:00
|
|
|
account = Account.where(id: id).first
|
2012-09-05 05:45:14 +08:00
|
|
|
account.precache if account
|
|
|
|
account || :nil
|
2012-02-07 02:36:46 +08:00
|
|
|
end
|
2012-09-05 05:45:14 +08:00
|
|
|
account = nil if account == :nil
|
|
|
|
account
|
|
|
|
end
|
|
|
|
|
2014-11-05 01:50:55 +08:00
|
|
|
def self.get_special_account(special_account_type, default_account_name, force_create = false)
|
2013-04-19 02:34:50 +08:00
|
|
|
Shard.birth.activate do
|
2014-08-26 22:53:26 +08:00
|
|
|
account = special_accounts[special_account_type]
|
2012-09-05 05:45:14 +08:00
|
|
|
unless account
|
2014-08-26 22:53:26 +08:00
|
|
|
special_account_id = special_account_ids[special_account_type] ||= Setting.get("#{special_account_type}_account_id", nil)
|
|
|
|
account = special_accounts[special_account_type] = Account.find_cached(special_account_id) if special_account_id
|
2012-04-06 03:03:22 +08:00
|
|
|
end
|
2012-09-05 05:45:14 +08:00
|
|
|
# another process (i.e. selenium spec) may have changed the setting
|
|
|
|
unless account
|
|
|
|
special_account_id = Setting.get("#{special_account_type}_account_id", nil)
|
2014-08-26 22:53:26 +08:00
|
|
|
if special_account_id && special_account_id != special_account_ids[special_account_type]
|
|
|
|
special_account_ids[special_account_type] = special_account_id
|
2014-09-12 03:44:34 +08:00
|
|
|
account = special_accounts[special_account_type] = Account.where(id: special_account_id).first
|
2012-09-05 05:45:14 +08:00
|
|
|
end
|
|
|
|
end
|
2014-11-05 01:50:55 +08:00
|
|
|
if !account && default_account_name && ((!special_account_id && !Rails.env.production?) || force_create)
|
2012-09-05 05:45:14 +08:00
|
|
|
# TODO i18n
|
|
|
|
t '#account.default_site_administrator_account_name', 'Site Admin'
|
|
|
|
t '#account.default_account_name', 'Default Account'
|
2014-08-26 22:53:26 +08:00
|
|
|
account = special_accounts[special_account_type] = Account.new(:name => default_account_name)
|
2013-03-15 05:31:31 +08:00
|
|
|
account.save!
|
2012-09-05 05:45:14 +08:00
|
|
|
Setting.set("#{special_account_type}_account_id", account.id)
|
2014-08-26 22:53:26 +08:00
|
|
|
special_account_ids[special_account_type] = account.id
|
2012-09-05 05:45:14 +08:00
|
|
|
end
|
|
|
|
account
|
2012-04-06 03:03:22 +08:00
|
|
|
end
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
|
|
|
|
2011-02-11 03:13:02 +08:00
|
|
|
def site_admin?
|
|
|
|
self == Account.site_admin
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def display_name
|
|
|
|
self.name
|
|
|
|
end
|
|
|
|
|
|
|
|
# Updates account associations for all the courses and users associated with this account
|
|
|
|
def update_account_associations
|
2013-01-26 01:32:08 +08:00
|
|
|
self.shard.activate do
|
|
|
|
account_chain_cache = {}
|
2013-06-04 04:52:42 +08:00
|
|
|
all_user_ids = Set.new
|
|
|
|
|
|
|
|
# make sure to use the non-associated_courses associations
|
|
|
|
# to catch courses that didn't ever have an association created
|
|
|
|
scopes = if root_account?
|
2013-06-04 05:04:55 +08:00
|
|
|
[all_courses,
|
2013-06-04 04:52:42 +08:00
|
|
|
associated_courses.
|
2013-06-04 05:04:55 +08:00
|
|
|
where("root_account_id<>?", self)]
|
2013-06-04 04:52:42 +08:00
|
|
|
else
|
2013-06-04 05:04:55 +08:00
|
|
|
[courses,
|
2013-06-04 04:52:42 +08:00
|
|
|
associated_courses.
|
2013-06-04 05:04:55 +08:00
|
|
|
where("courses.account_id<>?", self)]
|
2013-06-04 04:52:42 +08:00
|
|
|
end
|
|
|
|
# match the "batch" size in Course.update_account_associations
|
|
|
|
scopes.each do |scope|
|
2013-06-04 05:04:55 +08:00
|
|
|
scope.select([:id, :account_id]).find_in_batches(:batch_size => 500) do |courses|
|
2013-07-17 00:27:51 +08:00
|
|
|
all_user_ids.merge Course.update_account_associations(courses, :skip_user_account_associations => true, :account_chain_cache => account_chain_cache)
|
2013-06-04 04:52:42 +08:00
|
|
|
end
|
|
|
|
end
|
2011-08-18 03:33:10 +08:00
|
|
|
|
2013-01-26 01:32:08 +08:00
|
|
|
# Make sure we have all users with existing account associations.
|
2013-06-04 04:52:42 +08:00
|
|
|
all_user_ids.merge self.user_account_associations.pluck(:user_id)
|
|
|
|
if root_account?
|
|
|
|
all_user_ids.merge self.pseudonyms.active.pluck(:user_id)
|
|
|
|
end
|
2011-08-18 03:33:10 +08:00
|
|
|
|
2013-01-26 01:32:08 +08:00
|
|
|
# Update the users' associations as well
|
2013-06-04 04:52:42 +08:00
|
|
|
User.update_account_associations(all_user_ids.to_a, :account_chain_cache => account_chain_cache)
|
2013-01-26 01:32:08 +08:00
|
|
|
end
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
# this will take an account and make it a sub_account of
|
|
|
|
# itself. Also updates all it's descendant accounts to point to
|
|
|
|
# the correct root account, and updates the pseudonyms to
|
|
|
|
# points to the new root account as well.
|
|
|
|
def consume_account(account)
|
|
|
|
account.all_accounts.each do |sub_account|
|
2011-12-28 05:57:56 +08:00
|
|
|
sub_account.root_account = self.root_account
|
2011-02-01 09:57:29 +08:00
|
|
|
sub_account.save!
|
|
|
|
end
|
|
|
|
account.parent_account = self
|
2011-12-28 05:57:56 +08:00
|
|
|
account.root_account = self.root_account
|
2011-02-01 09:57:29 +08:00
|
|
|
account.save!
|
|
|
|
account.pseudonyms.each do |pseudonym|
|
2011-12-28 05:57:56 +08:00
|
|
|
pseudonym.account = self.root_account
|
2011-02-01 09:57:29 +08:00
|
|
|
pseudonym.save!
|
|
|
|
end
|
|
|
|
end
|
2011-12-28 05:38:15 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def course_count
|
2015-05-06 04:38:04 +08:00
|
|
|
self.courses.active.count
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
2013-12-04 07:00:14 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def sub_account_count
|
|
|
|
self.sub_accounts.active.count
|
|
|
|
end
|
2011-08-30 02:25:20 +08:00
|
|
|
|
|
|
|
def user_count
|
|
|
|
self.user_account_associations.count
|
|
|
|
end
|
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def current_sis_batch
|
2011-04-27 11:55:24 +08:00
|
|
|
if (current_sis_batch_id = self.read_attribute(:current_sis_batch_id)) && current_sis_batch_id.present?
|
2014-09-12 03:44:34 +08:00
|
|
|
self.sis_batches.where(id: current_sis_batch_id).first
|
2011-04-27 11:55:24 +08:00
|
|
|
end
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def turnitin_settings
|
2013-12-04 07:00:14 +08:00
|
|
|
return @turnitin_settings if defined?(@turnitin_settings)
|
2013-01-05 07:11:25 +08:00
|
|
|
if self.turnitin_account_id.present? && self.turnitin_shared_secret.present?
|
2013-12-04 07:00:14 +08:00
|
|
|
@turnitin_settings = [self.turnitin_account_id, self.turnitin_shared_secret, self.turnitin_host]
|
2011-02-01 09:57:29 +08:00
|
|
|
else
|
2013-12-04 07:00:14 +08:00
|
|
|
@turnitin_settings = self.parent_account.try(:turnitin_settings)
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def closest_turnitin_pledge
|
|
|
|
if self.turnitin_pledge && !self.turnitin_pledge.empty?
|
|
|
|
self.turnitin_pledge
|
|
|
|
else
|
2013-01-09 04:00:14 +08:00
|
|
|
res = self.parent_account.try(:closest_turnitin_pledge)
|
2011-06-16 04:39:32 +08:00
|
|
|
res ||= t('#account.turnitin_pledge', "This assignment submission is my own, original work")
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def closest_turnitin_comments
|
|
|
|
if self.turnitin_comments && !self.turnitin_comments.empty?
|
|
|
|
self.turnitin_comments
|
|
|
|
else
|
2013-07-03 02:37:29 +08:00
|
|
|
self.parent_account.try(:closest_turnitin_comments)
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2011-03-27 11:38:54 +08:00
|
|
|
def self_enrollment_allowed?(course)
|
|
|
|
if !settings[:self_enrollment].blank?
|
|
|
|
!!(settings[:self_enrollment] == 'any' || (!course.sis_source_id && settings[:self_enrollment] == 'manually_created'))
|
|
|
|
else
|
|
|
|
!!(parent_account && parent_account.self_enrollment_allowed?(course))
|
|
|
|
end
|
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2014-08-21 21:07:27 +08:00
|
|
|
def allow_self_enrollment!(setting='any')
|
|
|
|
settings[:self_enrollment] = setting
|
|
|
|
self.save!
|
|
|
|
end
|
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
TAB_COURSES = 0
|
|
|
|
TAB_STATISTICS = 1
|
|
|
|
TAB_PERMISSIONS = 2
|
|
|
|
TAB_SUB_ACCOUNTS = 3
|
|
|
|
TAB_TERMS = 4
|
|
|
|
TAB_AUTHENTICATION = 5
|
|
|
|
TAB_USERS = 6
|
|
|
|
TAB_OUTCOMES = 7
|
|
|
|
TAB_RUBRICS = 8
|
|
|
|
TAB_SETTINGS = 9
|
|
|
|
TAB_FACULTY_JOURNAL = 10
|
|
|
|
TAB_SIS_IMPORT = 11
|
2011-04-09 06:45:38 +08:00
|
|
|
TAB_GRADING_STANDARDS = 12
|
2011-08-31 07:24:15 +08:00
|
|
|
TAB_QUESTION_BANKS = 13
|
2012-01-21 05:56:07 +08:00
|
|
|
# site admin tabs
|
|
|
|
TAB_PLUGINS = 14
|
|
|
|
TAB_JOBS = 15
|
2012-05-17 13:52:32 +08:00
|
|
|
TAB_DEVELOPER_KEYS = 16
|
2013-03-05 01:20:41 +08:00
|
|
|
TAB_ADMIN_TOOLS = 17
|
2011-07-26 00:12:55 +08:00
|
|
|
|
basic lti navigation links
By properly configuring external tools (see
/spec/models/course_spec/rb:898 for examples) they can
be added as left-side navigation links to a course,
an account, or to the user profile section of Canvas.
testing notes:
- you have to manually set options on the external tool:
- for user navigation the tool needs to be created on the root account
with the following settings:
{:user_navigation => {:url => <url>, :text => <tab label>} }
(there are also some optional language options you can set using
the :labels attribute)
- for account navigation it's the same
- for course navigation it's the same, except with :course_navigation
there's also some additional options:
:visibility => <value> // public, members, admins
:default => <value> // disabled, enabled
test plan:
- configure a user navigation tool at the root account level,
make sure it shows up in the user's profile section
- configure a course navigation tool at the account level,
make sure it shows up in the course's navigation
- configure a course navigation tool at the course level,
make sure it shows up in the course's navigation
- make sure :default => 'disabled' course navigation tools don't
appear by default in the navigation, but can be enabled on
the course settings page
- make sure :visibility => 'members' only shows up for course members
- make sure :visibility => 'admins' only shows up for course admins
- configure an account navigation tool at the account level,
make sure it shows up in the account's navigation, and
any sub-account's navigation
Change-Id: I977da3c6b89a9e32b4cff4c2b6b221f8162782ff
Reviewed-on: https://gerrit.instructure.com/5427
Reviewed-by: Brian Whitmer <brian@instructure.com>
Tested-by: Hudson <hudson@instructure.com>
2011-08-18 13:49:01 +08:00
|
|
|
def external_tool_tabs(opts)
|
2012-01-18 13:32:57 +08:00
|
|
|
tools = ContextExternalTool.active.find_all_for(self, :account_navigation)
|
basic lti navigation links
By properly configuring external tools (see
/spec/models/course_spec/rb:898 for examples) they can
be added as left-side navigation links to a course,
an account, or to the user profile section of Canvas.
testing notes:
- you have to manually set options on the external tool:
- for user navigation the tool needs to be created on the root account
with the following settings:
{:user_navigation => {:url => <url>, :text => <tab label>} }
(there are also some optional language options you can set using
the :labels attribute)
- for account navigation it's the same
- for course navigation it's the same, except with :course_navigation
there's also some additional options:
:visibility => <value> // public, members, admins
:default => <value> // disabled, enabled
test plan:
- configure a user navigation tool at the root account level,
make sure it shows up in the user's profile section
- configure a course navigation tool at the account level,
make sure it shows up in the course's navigation
- configure a course navigation tool at the course level,
make sure it shows up in the course's navigation
- make sure :default => 'disabled' course navigation tools don't
appear by default in the navigation, but can be enabled on
the course settings page
- make sure :visibility => 'members' only shows up for course members
- make sure :visibility => 'admins' only shows up for course admins
- configure an account navigation tool at the account level,
make sure it shows up in the account's navigation, and
any sub-account's navigation
Change-Id: I977da3c6b89a9e32b4cff4c2b6b221f8162782ff
Reviewed-on: https://gerrit.instructure.com/5427
Reviewed-by: Brian Whitmer <brian@instructure.com>
Tested-by: Hudson <hudson@instructure.com>
2011-08-18 13:49:01 +08:00
|
|
|
tools.sort_by(&:id).map do |tool|
|
|
|
|
{
|
|
|
|
:id => tool.asset_string,
|
2015-06-16 03:58:15 +08:00
|
|
|
:label => tool.label_for(:account_navigation, opts[:language] || I18n.locale),
|
basic lti navigation links
By properly configuring external tools (see
/spec/models/course_spec/rb:898 for examples) they can
be added as left-side navigation links to a course,
an account, or to the user profile section of Canvas.
testing notes:
- you have to manually set options on the external tool:
- for user navigation the tool needs to be created on the root account
with the following settings:
{:user_navigation => {:url => <url>, :text => <tab label>} }
(there are also some optional language options you can set using
the :labels attribute)
- for account navigation it's the same
- for course navigation it's the same, except with :course_navigation
there's also some additional options:
:visibility => <value> // public, members, admins
:default => <value> // disabled, enabled
test plan:
- configure a user navigation tool at the root account level,
make sure it shows up in the user's profile section
- configure a course navigation tool at the account level,
make sure it shows up in the course's navigation
- configure a course navigation tool at the course level,
make sure it shows up in the course's navigation
- make sure :default => 'disabled' course navigation tools don't
appear by default in the navigation, but can be enabled on
the course settings page
- make sure :visibility => 'members' only shows up for course members
- make sure :visibility => 'admins' only shows up for course admins
- configure an account navigation tool at the account level,
make sure it shows up in the account's navigation, and
any sub-account's navigation
Change-Id: I977da3c6b89a9e32b4cff4c2b6b221f8162782ff
Reviewed-on: https://gerrit.instructure.com/5427
Reviewed-by: Brian Whitmer <brian@instructure.com>
Tested-by: Hudson <hudson@instructure.com>
2011-08-18 13:49:01 +08:00
|
|
|
:css_class => tool.asset_string,
|
2014-11-22 05:14:42 +08:00
|
|
|
:visibility => tool.account_navigation(:visibility),
|
basic lti navigation links
By properly configuring external tools (see
/spec/models/course_spec/rb:898 for examples) they can
be added as left-side navigation links to a course,
an account, or to the user profile section of Canvas.
testing notes:
- you have to manually set options on the external tool:
- for user navigation the tool needs to be created on the root account
with the following settings:
{:user_navigation => {:url => <url>, :text => <tab label>} }
(there are also some optional language options you can set using
the :labels attribute)
- for account navigation it's the same
- for course navigation it's the same, except with :course_navigation
there's also some additional options:
:visibility => <value> // public, members, admins
:default => <value> // disabled, enabled
test plan:
- configure a user navigation tool at the root account level,
make sure it shows up in the user's profile section
- configure a course navigation tool at the account level,
make sure it shows up in the course's navigation
- configure a course navigation tool at the course level,
make sure it shows up in the course's navigation
- make sure :default => 'disabled' course navigation tools don't
appear by default in the navigation, but can be enabled on
the course settings page
- make sure :visibility => 'members' only shows up for course members
- make sure :visibility => 'admins' only shows up for course admins
- configure an account navigation tool at the account level,
make sure it shows up in the account's navigation, and
any sub-account's navigation
Change-Id: I977da3c6b89a9e32b4cff4c2b6b221f8162782ff
Reviewed-on: https://gerrit.instructure.com/5427
Reviewed-by: Brian Whitmer <brian@instructure.com>
Tested-by: Hudson <hudson@instructure.com>
2011-08-18 13:49:01 +08:00
|
|
|
:href => :account_external_tool_path,
|
|
|
|
:external => true,
|
|
|
|
:args => [self.id, tool.id]
|
|
|
|
}
|
|
|
|
end
|
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def tabs_available(user=nil, opts={})
|
2014-05-03 00:35:29 +08:00
|
|
|
manage_settings = user && self.grants_right?(user, :manage_account_settings)
|
2011-02-11 03:13:02 +08:00
|
|
|
if site_admin?
|
2011-08-12 04:50:02 +08:00
|
|
|
tabs = []
|
2014-05-03 00:35:29 +08:00
|
|
|
tabs << { :id => TAB_USERS, :label => t('#account.tab_users', "Users"), :css_class => 'users', :href => :account_users_path } if user && self.grants_right?(user, :read_roster)
|
|
|
|
tabs << { :id => TAB_PERMISSIONS, :label => t('#account.tab_permissions', "Permissions"), :css_class => 'permissions', :href => :account_permissions_path } if user && self.grants_right?(user, :manage_role_overrides)
|
2013-01-16 01:05:31 +08:00
|
|
|
tabs << { :id => TAB_SUB_ACCOUNTS, :label => t('#account.tab_sub_accounts', "Sub-Accounts"), :css_class => 'sub_accounts', :href => :account_sub_accounts_path } if manage_settings
|
2011-12-14 02:58:11 +08:00
|
|
|
tabs << { :id => TAB_AUTHENTICATION, :label => t('#account.tab_authentication', "Authentication"), :css_class => 'authentication', :href => :account_account_authorization_configs_path } if manage_settings
|
2014-05-03 00:35:29 +08:00
|
|
|
tabs << { :id => TAB_PLUGINS, :label => t("#account.tab_plugins", "Plugins"), :css_class => "plugins", :href => :plugins_path, :no_args => true } if self.grants_right?(user, :manage_site_settings)
|
|
|
|
tabs << { :id => TAB_JOBS, :label => t("#account.tab_jobs", "Jobs"), :css_class => "jobs", :href => :jobs_path, :no_args => true } if self.grants_right?(user, :view_jobs)
|
|
|
|
tabs << { :id => TAB_DEVELOPER_KEYS, :label => t("#account.tab_developer_keys", "Developer Keys"), :css_class => "developer_keys", :href => :developer_keys_path, :no_args => true } if self.grants_right?(user, :manage_developer_keys)
|
2011-02-11 03:13:02 +08:00
|
|
|
else
|
2011-08-12 04:50:02 +08:00
|
|
|
tabs = []
|
2014-05-03 00:35:29 +08:00
|
|
|
tabs << { :id => TAB_COURSES, :label => t('#account.tab_courses', "Courses"), :css_class => 'courses', :href => :account_path } if user && self.grants_right?(user, :read_course_list)
|
|
|
|
tabs << { :id => TAB_USERS, :label => t('#account.tab_users', "Users"), :css_class => 'users', :href => :account_users_path } if user && self.grants_right?(user, :read_roster)
|
|
|
|
tabs << { :id => TAB_STATISTICS, :label => t('#account.tab_statistics', "Statistics"), :css_class => 'statistics', :href => :statistics_account_path } if user && self.grants_right?(user, :view_statistics)
|
|
|
|
tabs << { :id => TAB_PERMISSIONS, :label => t('#account.tab_permissions', "Permissions"), :css_class => 'permissions', :href => :account_permissions_path } if user && self.grants_right?(user, :manage_role_overrides)
|
|
|
|
if user && self.grants_right?(user, :manage_outcomes)
|
2011-08-06 05:14:21 +08:00
|
|
|
tabs << { :id => TAB_OUTCOMES, :label => t('#account.tab_outcomes', "Outcomes"), :css_class => 'outcomes', :href => :account_outcomes_path }
|
|
|
|
tabs << { :id => TAB_RUBRICS, :label => t('#account.tab_rubrics', "Rubrics"), :css_class => 'rubrics', :href => :account_rubrics_path }
|
fix up account level permissions fixes #4478
So, we have several account level permissions, we just weren't
respecting them. Most notably is :manage_account_settings instead
of :manage, the generic permission.
Account users get all the generic permissions (:create, :read,
:update, :delete, :manage) because there are still lots of course
level things that check those permissions. We still want to keep
those intact until we fix all those other checks, so for account
level things we need to use specific permissions as much as possible.
Things that are either odd or not correctly checked (due to having
to work with courses as a context with the generic permission):
* Listing and searching Courses, and viewing Statistics, is
available to any account admin, because there aren't specific
permissions for them
* Rubrics are not linked to without :manage_outcomes, but are
accesible via direct URI
* External tools is available to any account admin
* Account reports uses the :read_reports permission, which is
described in the UI as "View usage reports for the course"
Change-Id: Ia0f9409659dfc421f1199f7c8ab93b43edcde511
Reviewed-on: https://gerrit.instructure.com/3735
Reviewed-by: Brian Palmer <brianp@instructure.com>
Tested-by: Hudson <hudson@instructure.com>
2011-05-20 05:29:51 +08:00
|
|
|
end
|
reactify grading standards page
convert grading standards page to React code,
and add a tab for Grading Periods when MGP feature flag
is turned on.
closes CNVS-15966
test plan:
1) As an admin, visit the 'Account' home page (
for example, http://localhost:3000/accounts/1).
Go to settings and turn the feature flag on for
'Multiple Grading Periods'
2) Click on the link on the left-hand side that says
'Grading'
3) Verify that there are now 2 tabs - one for 'Grading Periods',
and one for 'Grading Schemes'. There should not be any
content in the 'Grading Periods' sectiion (this will be
added in a later ticket).
4) Click on the 'Grading Schemes' tab. Verify that the tab
is fully functional, i.e. you can create, edit, and delete
grading schemes. Follow this link to a Jing video for
more detail on what needs to be QA'd here (there is a lot).
(http://screencast.com/t/nsiCVsMjcdF).I changed the behavior
slightly from how it is portrayed in the video. Now, the
values will auto-recalculate when input is changed, as opposed
to changing when focus is lost on the input.
5) Verify when you hover over the '+' icon to add a new row to
the scheme, a line appears below the current row to show where
the new row will be inserted.
6) Verify that clicking "Add grading scheme" and then clicking
"Cancel" removes the new grading scheme, rather than resetting
the values of the scheme to default values (this behavior is
different from what was shown in the video, and was changed after
a talk with Product).
7) Check all functionality covered in the video is also accessible.
Change-Id: Iff61acf917bce297f7943ac758862fa56eed275c
Reviewed-on: https://gerrit.instructure.com/44667
Tested-by: Jenkins <jenkins@instructure.com>
Reviewed-by: Josh Simpson <jsimpson@instructure.com>
QA-Review: Amber Taniuchi <amber@instructure.com>
Product-Review: Spencer Olson <solson@instructure.com>
2014-11-20 06:29:55 +08:00
|
|
|
tabs << { :id => TAB_GRADING_STANDARDS, :label => t('#account.tab_grading_standards', "Grading"), :css_class => 'grading_standards', :href => :account_grading_standards_path } if user && self.grants_right?(user, :manage_grades)
|
2015-01-01 04:25:29 +08:00
|
|
|
tabs << { :id => TAB_QUESTION_BANKS, :label => t('#account.tab_question_banks', "Question Banks"), :css_class => 'question_banks', :href => :account_question_banks_path } if user && self.grants_right?(user, :manage_assignments)
|
2011-08-06 05:14:21 +08:00
|
|
|
tabs << { :id => TAB_SUB_ACCOUNTS, :label => t('#account.tab_sub_accounts', "Sub-Accounts"), :css_class => 'sub_accounts', :href => :account_sub_accounts_path } if manage_settings
|
2014-05-03 00:35:29 +08:00
|
|
|
tabs << { :id => TAB_FACULTY_JOURNAL, :label => t('#account.tab_faculty_journal', "Faculty Journal"), :css_class => 'faculty_journal', :href => :account_user_notes_path} if self.enable_user_notes && user && self.grants_right?(user, :manage_user_notes)
|
2011-12-14 02:58:11 +08:00
|
|
|
tabs << { :id => TAB_TERMS, :label => t('#account.tab_terms', "Terms"), :css_class => 'terms', :href => :account_terms_path } if self.root_account? && manage_settings
|
|
|
|
tabs << { :id => TAB_AUTHENTICATION, :label => t('#account.tab_authentication', "Authentication"), :css_class => 'authentication', :href => :account_account_authorization_configs_path } if self.root_account? && manage_settings
|
2014-05-03 00:35:29 +08:00
|
|
|
tabs << { :id => TAB_SIS_IMPORT, :label => t('#account.tab_sis_import', "SIS Import"), :css_class => 'sis_import', :href => :account_sis_import_path } if self.root_account? && self.allow_sis_import && user && self.grants_right?(user, :manage_sis)
|
2011-08-06 05:14:21 +08:00
|
|
|
end
|
basic lti navigation links
By properly configuring external tools (see
/spec/models/course_spec/rb:898 for examples) they can
be added as left-side navigation links to a course,
an account, or to the user profile section of Canvas.
testing notes:
- you have to manually set options on the external tool:
- for user navigation the tool needs to be created on the root account
with the following settings:
{:user_navigation => {:url => <url>, :text => <tab label>} }
(there are also some optional language options you can set using
the :labels attribute)
- for account navigation it's the same
- for course navigation it's the same, except with :course_navigation
there's also some additional options:
:visibility => <value> // public, members, admins
:default => <value> // disabled, enabled
test plan:
- configure a user navigation tool at the root account level,
make sure it shows up in the user's profile section
- configure a course navigation tool at the account level,
make sure it shows up in the course's navigation
- configure a course navigation tool at the course level,
make sure it shows up in the course's navigation
- make sure :default => 'disabled' course navigation tools don't
appear by default in the navigation, but can be enabled on
the course settings page
- make sure :visibility => 'members' only shows up for course members
- make sure :visibility => 'admins' only shows up for course admins
- configure an account navigation tool at the account level,
make sure it shows up in the account's navigation, and
any sub-account's navigation
Change-Id: I977da3c6b89a9e32b4cff4c2b6b221f8162782ff
Reviewed-on: https://gerrit.instructure.com/5427
Reviewed-by: Brian Whitmer <brian@instructure.com>
Tested-by: Hudson <hudson@instructure.com>
2011-08-18 13:49:01 +08:00
|
|
|
tabs += external_tool_tabs(opts)
|
2014-10-02 00:38:48 +08:00
|
|
|
tabs += Lti::MessageHandler.lti_apps_tabs(self, [Lti::ResourcePlacement::ACCOUNT_NAVIGATION], opts)
|
2013-03-05 01:20:41 +08:00
|
|
|
tabs << { :id => TAB_ADMIN_TOOLS, :label => t('#account.tab_admin_tools', "Admin Tools"), :css_class => 'admin_tools', :href => :account_admin_tools_path } if can_see_admin_tools_tab?(user)
|
2011-08-06 05:14:21 +08:00
|
|
|
tabs << { :id => TAB_SETTINGS, :label => t('#account.tab_settings', "Settings"), :css_class => 'settings', :href => :account_settings_path }
|
2014-11-22 05:14:42 +08:00
|
|
|
tabs.delete_if{ |t| t[:visibility] == 'admins' } unless self.grants_right?(user, :manage)
|
2011-02-01 09:57:29 +08:00
|
|
|
tabs
|
|
|
|
end
|
|
|
|
|
2013-03-05 01:20:41 +08:00
|
|
|
def can_see_admin_tools_tab?(user)
|
|
|
|
return false if !user || site_admin?
|
|
|
|
admin_tool_permissions = RoleOverride.manageable_permissions(self).find_all{|p| p[1][:admin_tool]}
|
|
|
|
admin_tool_permissions.any? do |p|
|
2014-05-03 00:35:29 +08:00
|
|
|
self.grants_right?(user, p.first)
|
2013-03-05 01:20:41 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def is_a_context?
|
|
|
|
true
|
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2011-11-11 10:43:36 +08:00
|
|
|
def help_links
|
2012-12-28 06:32:08 +08:00
|
|
|
Canvas::Help.default_links + (settings[:custom_help_links] || [])
|
2011-08-27 14:18:36 +08:00
|
|
|
end
|
2012-06-14 09:59:02 +08:00
|
|
|
|
2011-03-09 03:12:38 +08:00
|
|
|
def set_service_availability(service, enable)
|
|
|
|
service = service.to_sym
|
2015-04-10 05:51:22 +08:00
|
|
|
raise "Invalid Service" unless AccountServices.allowable_services[service]
|
2011-03-09 03:12:38 +08:00
|
|
|
allowed_service_names = (self.allowed_services || "").split(",").compact
|
2015-04-10 05:51:22 +08:00
|
|
|
if allowed_service_names.count > 0 && ![ '+', '-' ].include?(allowed_service_names[0][0,1])
|
2011-03-09 03:12:38 +08:00
|
|
|
# This account has a hard-coded list of services, so handle accordingly
|
2011-09-09 01:12:25 +08:00
|
|
|
allowed_service_names.reject! { |flag| flag.match("^[+-]?#{service}$") }
|
2011-03-09 03:12:38 +08:00
|
|
|
allowed_service_names << service if enable
|
|
|
|
else
|
2011-09-09 01:12:25 +08:00
|
|
|
allowed_service_names.reject! { |flag| flag.match("^[+-]?#{service}$") }
|
2011-03-09 03:12:38 +08:00
|
|
|
if enable
|
|
|
|
# only enable if it is not enabled by default
|
2015-04-10 05:51:22 +08:00
|
|
|
allowed_service_names << "+#{service}" unless AccountServices.default_allowable_services[service]
|
2011-03-09 03:12:38 +08:00
|
|
|
else
|
|
|
|
# only disable if it is not enabled by default
|
2015-04-10 05:51:22 +08:00
|
|
|
allowed_service_names << "-#{service}" if AccountServices.default_allowable_services[service]
|
2011-03-09 03:12:38 +08:00
|
|
|
end
|
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2011-03-09 03:12:38 +08:00
|
|
|
@allowed_services_hash = nil
|
|
|
|
self.allowed_services = allowed_service_names.empty? ? nil : allowed_service_names.join(",")
|
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2011-03-09 03:12:38 +08:00
|
|
|
def enable_service(service)
|
|
|
|
set_service_availability(service, true)
|
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2011-03-09 03:12:38 +08:00
|
|
|
def disable_service(service)
|
|
|
|
set_service_availability(service, false)
|
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def allowed_services_hash
|
|
|
|
return @allowed_services_hash if @allowed_services_hash
|
2015-04-10 05:51:22 +08:00
|
|
|
account_allowed_services = AccountServices.default_allowable_services
|
2011-02-01 09:57:29 +08:00
|
|
|
if self.allowed_services
|
|
|
|
allowed_service_names = self.allowed_services.split(",").compact
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
if allowed_service_names.count > 0
|
|
|
|
unless [ '+', '-' ].member?(allowed_service_names[0][0,1])
|
|
|
|
# This account has a hard-coded list of services, so we clear out the defaults
|
|
|
|
account_allowed_services = { }
|
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
allowed_service_names.each do |service_switch|
|
|
|
|
if service_switch =~ /\A([+-]?)(.*)\z/
|
|
|
|
flag = $1
|
|
|
|
service_name = $2.to_sym
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
if flag == '-'
|
|
|
|
account_allowed_services.delete(service_name)
|
|
|
|
else
|
2015-04-10 05:51:22 +08:00
|
|
|
account_allowed_services[service_name] = AccountServices.allowable_services[service_name]
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
@allowed_services_hash = account_allowed_services
|
|
|
|
end
|
2012-06-14 09:59:02 +08:00
|
|
|
|
|
|
|
# if expose_as is nil, all services exposed in the ui are returned
|
|
|
|
# if it's :service or :setting, then only services set to be exposed as that type are returned
|
2013-06-13 06:42:36 +08:00
|
|
|
def self.services_exposed_to_ui_hash(expose_as = nil, current_user = nil, account = nil)
|
2012-06-14 09:59:02 +08:00
|
|
|
if expose_as
|
2015-04-10 05:51:22 +08:00
|
|
|
AccountServices.allowable_services.reject { |_, setting| setting[:expose_to_ui] != expose_as }
|
2012-06-14 09:59:02 +08:00
|
|
|
else
|
2015-04-10 05:51:22 +08:00
|
|
|
AccountServices.allowable_services.reject { |_, setting| !setting[:expose_to_ui] }
|
|
|
|
end.reject { |_, setting| setting[:expose_to_ui_proc] && !setting[:expose_to_ui_proc].call(current_user, account) }
|
2011-03-09 03:12:38 +08:00
|
|
|
end
|
2013-06-07 00:31:23 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def service_enabled?(service)
|
|
|
|
service = service.to_sym
|
|
|
|
case service
|
|
|
|
when :none
|
|
|
|
self.allowed_services_hash.empty?
|
|
|
|
else
|
|
|
|
self.allowed_services_hash.has_key?(service)
|
|
|
|
end
|
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2011-04-09 06:45:38 +08:00
|
|
|
def self.all_accounts_for(context)
|
|
|
|
if context.respond_to?(:account)
|
|
|
|
context.account.account_chain
|
|
|
|
elsif context.respond_to?(:parent_account)
|
|
|
|
context.account_chain
|
|
|
|
else
|
|
|
|
[]
|
|
|
|
end
|
|
|
|
end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def self.serialization_excludes; [:uuid]; end
|
2014-05-03 00:35:29 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
# This could be much faster if we implement a SQL tree for the account tree
|
|
|
|
# structure.
|
|
|
|
def find_child(child_id)
|
|
|
|
child_id = child_id.to_i
|
|
|
|
child_ids = self.class.connection.select_values("SELECT id FROM accounts WHERE parent_account_id = #{self.id}").map(&:to_i)
|
|
|
|
until child_ids.empty?
|
|
|
|
if child_ids.include?(child_id)
|
|
|
|
return self.class.find(child_id)
|
|
|
|
end
|
|
|
|
child_ids = self.class.connection.select_values("SELECT id FROM accounts WHERE parent_account_id IN (#{child_ids.join(",")})").map(&:to_i)
|
|
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
2011-08-10 06:09:17 +08:00
|
|
|
def manually_created_courses_account
|
2012-09-05 07:00:13 +08:00
|
|
|
return self.root_account.manually_created_courses_account unless self.root_account?
|
|
|
|
display_name = t('#account.manually_created_courses', "Manually-Created Courses")
|
|
|
|
acct = manually_created_courses_account_from_settings
|
|
|
|
if acct.blank?
|
|
|
|
transaction do
|
|
|
|
lock!
|
|
|
|
acct = manually_created_courses_account_from_settings
|
2014-09-12 03:44:34 +08:00
|
|
|
acct ||= self.sub_accounts.where(name: display_name).first_or_create! # for backwards compatibility
|
2012-09-05 07:00:13 +08:00
|
|
|
if acct.id != self.settings[:manually_created_courses_account_id]
|
|
|
|
self.settings[:manually_created_courses_account_id] = acct.id
|
|
|
|
self.save!
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
acct
|
|
|
|
end
|
|
|
|
|
|
|
|
def manually_created_courses_account_from_settings
|
|
|
|
acct_id = self.settings[:manually_created_courses_account_id]
|
2014-09-12 03:44:34 +08:00
|
|
|
acct = self.sub_accounts.where(id: acct_id).first if acct_id.present?
|
2012-09-05 07:00:13 +08:00
|
|
|
acct = nil if acct.present? && acct.root_account_id != self.id
|
|
|
|
acct
|
2011-08-10 06:09:17 +08:00
|
|
|
end
|
2012-09-05 07:00:13 +08:00
|
|
|
private :manually_created_courses_account_from_settings
|
2011-08-10 06:09:17 +08:00
|
|
|
|
2012-02-17 01:45:42 +08:00
|
|
|
def trusted_account_ids
|
|
|
|
return [] if !root_account? || self == Account.site_admin
|
|
|
|
[ Account.site_admin.id ]
|
|
|
|
end
|
|
|
|
|
2014-03-13 03:52:36 +08:00
|
|
|
def trust_exists?
|
|
|
|
false
|
|
|
|
end
|
|
|
|
|
introduce preferred user list search mode
closes #7059, #7411
preferred is a compromise between open registration and closed
registration. if a single user is found, that user is found without
introducing any temporary user to allow user disambiguation. if no
users are found, or multiple users are found, it's the same behavior
as open registration.
as part of this, the "only search existing users" checkbox has been
removed. instead, preferred searching will be used in the following
cases:
* adding an admin, and open registration is enabled
* adding an admin, open registration is disabled, and the user
has permission to create new users
* adding a user to a course, open registration is enabled, and
delegated authentication is in use
* adding a user to a course, open registration is disabled, and
the user has permission to create new users
the notable case that preferred searching is not used, and the
user would want to use search existing users is open registration
enabled, and adding a user to a course. in this case, a temporary
user will be created instead of sending the invitation to the
existing user. however, the end user experience is identical
(invitation sent to e-mail, invitation visible in the same locations
in the UI), with the small exception that the end user gets the
opportunity to create a separate account if desired (but not
preferred).
test plan:
* go through each of the cases above for a user that doesn't exist
yet, a single matching user exists, or multiple users exist.
it should behave as described above
Change-Id: Idbc7fe23bfc7430b4cd25f7981f5dc08b9e4ffda
Reviewed-on: https://gerrit.instructure.com/8966
Tested-by: Hudson <hudson@instructure.com>
Reviewed-by: Brian Palmer <brianp@instructure.com>
2012-02-24 06:24:48 +08:00
|
|
|
def user_list_search_mode_for(user)
|
|
|
|
return :preferred if self.root_account.open_registration?
|
|
|
|
return :preferred if self.root_account.grants_right?(user, :manage_user_logins)
|
|
|
|
:closed
|
|
|
|
end
|
|
|
|
|
2014-07-02 03:38:26 +08:00
|
|
|
scope :root_accounts, -> { where(:root_account_id => nil) }
|
|
|
|
scope :processing_sis_batch, -> { where("accounts.current_sis_batch_id IS NOT NULL").order(:updated_at) }
|
|
|
|
scope :name_like, lambda { |name| where(wildcard('accounts.name', name)) }
|
|
|
|
scope :active, -> { where("accounts.workflow_state<>'deleted'") }
|
2012-06-27 00:45:12 +08:00
|
|
|
|
|
|
|
def canvas_network_enabled?
|
|
|
|
false
|
|
|
|
end
|
2012-08-21 21:57:13 +08:00
|
|
|
|
2013-09-14 02:02:36 +08:00
|
|
|
def change_root_account_setting!(setting_name, new_value)
|
|
|
|
root_account.settings[setting_name] = new_value
|
2013-08-27 05:57:41 +08:00
|
|
|
root_account.save!
|
|
|
|
end
|
2013-09-14 02:02:36 +08:00
|
|
|
|
2013-11-01 00:47:22 +08:00
|
|
|
Bookmarker = BookmarkedCollection::SimpleBookmarker.new(Account, :name, :id)
|
2014-12-16 02:43:45 +08:00
|
|
|
|
|
|
|
def format_referer(referer_url)
|
|
|
|
begin
|
|
|
|
referer = URI(referer_url || '')
|
|
|
|
rescue URI::InvalidURIError
|
|
|
|
return
|
|
|
|
end
|
|
|
|
return unless referer.host
|
|
|
|
|
|
|
|
referer_with_port = "#{referer.scheme}://#{referer.host}"
|
|
|
|
referer_with_port += ":#{referer.port}" unless referer.port == (referer.scheme == 'https' ? 443 : 80)
|
|
|
|
referer_with_port
|
|
|
|
end
|
|
|
|
|
|
|
|
def trusted_referers=(value)
|
|
|
|
self.settings[:trusted_referers] = unless value.blank?
|
|
|
|
value.split(',').map { |referer_url| format_referer(referer_url) }.compact.join(',')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def trusted_referer?(referer_url)
|
2015-01-10 23:33:53 +08:00
|
|
|
return if !self.settings.has_key?(:trusted_referers) || self.settings[:trusted_referers].blank?
|
2014-12-16 02:43:45 +08:00
|
|
|
if referer_with_port = format_referer(referer_url)
|
|
|
|
self.settings[:trusted_referers].split(',').include?(referer_with_port)
|
|
|
|
end
|
|
|
|
end
|
2015-03-03 01:41:17 +08:00
|
|
|
|
|
|
|
def parent_registration?
|
|
|
|
self.account_authorization_configs.exists?(parent_registration: true)
|
|
|
|
end
|
|
|
|
|
|
|
|
def parent_auth_type
|
|
|
|
return nil unless parent_registration?
|
|
|
|
parent_registration_aac.auth_type
|
|
|
|
end
|
|
|
|
|
|
|
|
def parent_registration_aac
|
|
|
|
account_authorization_configs.where(parent_registration: true).first
|
|
|
|
end
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|