canvas-lms/app/models/communication_channel.rb

487 lines
15 KiB
Ruby
Raw Normal View History

2011-02-01 09:57:29 +08:00
#
# Copyright (C) 2011 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
class CommunicationChannel < ActiveRecord::Base
# You should start thinking about communication channels
# as independent of pseudonyms
include Workflow
attr_accessible :user, :path, :path_type, :build_pseudonym_on_confirm, :pseudonym
Store information about the last bounce for a communication channel Fixes CNVS-20894 Test plan: - Set up outgoing email through the Amazon SES test account - Set up bounce_notifications.yml with the SQS test creds - Set a user's email address to bounce@simulator.amazonses.com - Cause a message to be sent to that user - Wait for the bounce notification processor to run (~5 minutes) - At a Rails console, check the CommunicationChannel's last_bounce_at and verify that it roughly corresponds to when the initial message was sent - Check the CommunicationChannel's last_bounce_details and ensure that they have been filled in - Cause another message to be sent to the user and wait for the bounce notification processor to run again - Check the CommunicationChannel's last_bounce_at and ensure that it has changed - Check the CommunicationChannel's last_bounce_details and ensure that they have also changed - Change the CommunicationChannel's path to be suppressionlist@simulator.amazonses.com - Cause another message to be sent to the user and wait for the bounce notification processor to run again - Check the CommunicationChannel's last_bounce_at and ensure that it has _not_ changed - Check the CommunicationChannel's last_suppression_bounce_at and ensure that it is now filled in - Check the CommunicationChannel's last_bounce_details and ensure that they have _not_ changed - Set another user's email address to success@simulator.amazonses.com - Cause a message to be sent for that user and ensure that it is sent successfully Change-Id: I771f59a5e9b33d415dd38c3d4a80f65fc1e003bd Reviewed-on: https://gerrit.instructure.com/55660 Reviewed-by: Jacob Fugal <jacob@instructure.com> Reviewed-by: Andrew Butterfield <abutterfield@instructure.com> Tested-by: Jenkins QA-Review: Heath Hales <hhales@instructure.com> Product-Review: Peyton Craighill <pcraighill@instructure.com>
2015-06-04 05:08:30 +08:00
serialize :last_bounce_details
serialize :last_transient_bounce_details
Store information about the last bounce for a communication channel Fixes CNVS-20894 Test plan: - Set up outgoing email through the Amazon SES test account - Set up bounce_notifications.yml with the SQS test creds - Set a user's email address to bounce@simulator.amazonses.com - Cause a message to be sent to that user - Wait for the bounce notification processor to run (~5 minutes) - At a Rails console, check the CommunicationChannel's last_bounce_at and verify that it roughly corresponds to when the initial message was sent - Check the CommunicationChannel's last_bounce_details and ensure that they have been filled in - Cause another message to be sent to the user and wait for the bounce notification processor to run again - Check the CommunicationChannel's last_bounce_at and ensure that it has changed - Check the CommunicationChannel's last_bounce_details and ensure that they have also changed - Change the CommunicationChannel's path to be suppressionlist@simulator.amazonses.com - Cause another message to be sent to the user and wait for the bounce notification processor to run again - Check the CommunicationChannel's last_bounce_at and ensure that it has _not_ changed - Check the CommunicationChannel's last_suppression_bounce_at and ensure that it is now filled in - Check the CommunicationChannel's last_bounce_details and ensure that they have _not_ changed - Set another user's email address to success@simulator.amazonses.com - Cause a message to be sent for that user and ensure that it is sent successfully Change-Id: I771f59a5e9b33d415dd38c3d4a80f65fc1e003bd Reviewed-on: https://gerrit.instructure.com/55660 Reviewed-by: Jacob Fugal <jacob@instructure.com> Reviewed-by: Andrew Butterfield <abutterfield@instructure.com> Tested-by: Jenkins QA-Review: Heath Hales <hhales@instructure.com> Product-Review: Peyton Craighill <pcraighill@instructure.com>
2015-06-04 05:08:30 +08:00
2011-02-01 09:57:29 +08:00
belongs_to :pseudonym
has_many :pseudonyms
belongs_to :user
has_many :notification_policies, :dependent => :destroy
has_many :delayed_messages, :dependent => :destroy
2011-02-01 09:57:29 +08:00
has_many :messages
user searching module refs #CNVS-2326 Added a UserSearch module that provides and interface for searching across the name, the email, and the sis id (and the database id), and added a trigram index on the columns that will be searched against. also made a slight refactor to the AR initializer in order to expose some of the behavior I wanted more granularly and added some specs to cover it fixed a small permissions bug in course.rb and pulled some scoping out of the courses controller down into the user search module TEST PLAN: This is currently not accessible from the site itself, this is just the ground work for the eventual user search api endpoint. The code is not called by anything currently in production either so there is no need for regression testing. The one thing to check would be the creation of new users (and pseudonyms and communication channels). There have been new indexes added to columns on those tables and there is some documentation indicating that writing to these indexes can be time consuming if the data set is quite large. It would be worth making sure that there have not been any unacceptable performance regressions in the creation of any of those record types in a database that has a full load of data (comparable to the production environment) Change-Id: I8fb13a6ec714f27efc8012c5ed2bed4d963c24e6 Reviewed-on: https://gerrit.instructure.com/16459 Tested-by: Jenkins <jenkins@instructure.com> QA-Review: Clare Hetherington <clare@instructure.com> Reviewed-by: Brian Palmer <brianp@instructure.com>
2012-12-29 03:28:09 +08:00
EXPORTABLE_ATTRIBUTES = [
:id, :path, :path_type, :position, :user_id, :pseudonym_id, :bounce_count, :workflow_state, :confirmation_code,
:created_at, :updated_at, :build_pseudonym_on_confirm
]
EXPORTABLE_ASSOCIATIONS = [:pseudonyms, :pseudonym, :user]
before_save :assert_path_type, :set_confirmation_code
2011-02-01 09:57:29 +08:00
before_save :consider_building_pseudonym
validates_presence_of :path, :path_type, :user, :workflow_state
validate :uniqueness_of_path
validate :not_otp_communication_channel, :if => lambda { |cc| cc.path_type == TYPE_SMS && cc.retired? && !cc.new_record? }
after_commit :check_if_bouncing_changed
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
acts_as_list :scope => :user
user searching module refs #CNVS-2326 Added a UserSearch module that provides and interface for searching across the name, the email, and the sis id (and the database id), and added a trigram index on the columns that will be searched against. also made a slight refactor to the AR initializer in order to expose some of the behavior I wanted more granularly and added some specs to cover it fixed a small permissions bug in course.rb and pulled some scoping out of the courses controller down into the user search module TEST PLAN: This is currently not accessible from the site itself, this is just the ground work for the eventual user search api endpoint. The code is not called by anything currently in production either so there is no need for regression testing. The one thing to check would be the creation of new users (and pseudonyms and communication channels). There have been new indexes added to columns on those tables and there is some documentation indicating that writing to these indexes can be time consuming if the data set is quite large. It would be worth making sure that there have not been any unacceptable performance regressions in the creation of any of those record types in a database that has a full load of data (comparable to the production environment) Change-Id: I8fb13a6ec714f27efc8012c5ed2bed4d963c24e6 Reviewed-on: https://gerrit.instructure.com/16459 Tested-by: Jenkins <jenkins@instructure.com> QA-Review: Clare Hetherington <clare@instructure.com> Reviewed-by: Brian Palmer <brianp@instructure.com>
2012-12-29 03:28:09 +08:00
2011-02-01 09:57:29 +08:00
has_a_broadcast_policy
user searching module refs #CNVS-2326 Added a UserSearch module that provides and interface for searching across the name, the email, and the sis id (and the database id), and added a trigram index on the columns that will be searched against. also made a slight refactor to the AR initializer in order to expose some of the behavior I wanted more granularly and added some specs to cover it fixed a small permissions bug in course.rb and pulled some scoping out of the courses controller down into the user search module TEST PLAN: This is currently not accessible from the site itself, this is just the ground work for the eventual user search api endpoint. The code is not called by anything currently in production either so there is no need for regression testing. The one thing to check would be the creation of new users (and pseudonyms and communication channels). There have been new indexes added to columns on those tables and there is some documentation indicating that writing to these indexes can be time consuming if the data set is quite large. It would be worth making sure that there have not been any unacceptable performance regressions in the creation of any of those record types in a database that has a full load of data (comparable to the production environment) Change-Id: I8fb13a6ec714f27efc8012c5ed2bed4d963c24e6 Reviewed-on: https://gerrit.instructure.com/16459 Tested-by: Jenkins <jenkins@instructure.com> QA-Review: Clare Hetherington <clare@instructure.com> Reviewed-by: Brian Palmer <brianp@instructure.com>
2012-12-29 03:28:09 +08:00
2011-02-01 09:57:29 +08:00
attr_reader :request_password
attr_reader :send_confirmation
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
# Constants for the different supported communication channels
TYPE_EMAIL = 'email'
TYPE_SMS = 'sms'
TYPE_TWITTER = 'twitter'
TYPE_PUSH = 'push'
TYPE_YO = 'yo'
RETIRE_THRESHOLD = 1
Allow entry of international phone numbers Fixes CNVS-20605 Test plan: - Enable the international SMS feature flag - You can do this from a console with this, assuming your root account's id is 1: Account.find(1).enable_feature!(:international_sms) - As a test user, pull up your settings page - Under "Other Contacts", click "Add Contact Method" - Verify that you see two fields, "Country" and "Cell Number" - Verify that the country names and country codes presented in the "Country" dropdown are as given in the AC of CNVS-20605 - Click "Reigster SMS" and verify you get a popup saying that "Country is required" - Select "Belgium (+32)" from the dropdown - Verify that no extra fields show up - Click "Register SMS" and verify that you get a popup saying that "Cell Number is required" - Enter "12345", then click "Register SMS" - Ensure that a box pops up saying that "We sent a four-character confirmation code to +3212345". - Close the dialog, then click "Add Contact Method" again - This time, select "United States (+1)" - Regression test this part of the dialog - it should work as it always has - Regression test adding email addresses via the "Email" tab as well - Disable the international SMS feature flag - You can do this with: Account.find(1).disable_feature!(:international_sms) - Regression test both adding email addresses and adding phone numbers and verify that they work as they did before, with no mention of the ability to select different countries Change-Id: I83982d1ee1fc3e22dced29fd28a714333e531899 Reviewed-on: https://gerrit.instructure.com/59811 Reviewed-by: Joel Hough <joel@instructure.com> Reviewed-by: Steven Burnett <sburnett@instructure.com> Tested-by: Jenkins QA-Review: Heath Hales <hhales@instructure.com> Product-Review: Matthew Wheeler <mwheeler@instructure.com>
2015-08-03 12:36:39 +08:00
# TODO: Will need to be internationalized. Also, do we want to allow this to be specified in a config file?
def self.country_codes
# [country code, name, true if email should be used instead of Twilio]
@country_codes ||= [
['54', 'Argentina', false],
['61', 'Australia', false],
['32', 'Belgium', false],
['55', 'Brazil', false],
['1', 'Canada', false],
['56', 'Chile', false],
['57', 'Colombia', false],
['45', 'Denmark', false],
['358', 'Finland', false],
['49', 'Germany', false],
['504', 'Honduras', false],
['852', 'Hong Kong', false],
['353', 'Ireland', false],
['352', 'Luxembourg', false],
['60', 'Malaysia', false],
['52', 'Mexico', false],
['31', 'Netherlands', false],
['64', 'New Zealand', false],
['47', 'Norway', false],
['507', 'Panama', false],
['51', 'Peru', false],
['63', 'Philippines', false],
['974', 'Qatar', false],
['966', 'Saudi Arabia', false],
['65', 'Singapore', false],
['34', 'Spain', false],
['46', 'Sweden', false],
['41', 'Switzerland', false],
['971', 'United Arab Emirates', false],
['44', 'United Kingdom', false],
['1', 'United States', true]
]
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 self.sms_carriers
@sms_carriers ||= Canvas::ICU.collate_by((ConfigFile.load('sms', false) ||
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
{ 'AT&T' => 'txt.att.net',
'Alltel' => 'message.alltel.com',
'Boost' => 'myboostmobile.com',
'C Spire' => 'cspire1.com',
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
'Cingular' => 'cingularme.com',
'CellularOne' => 'mobile.celloneusa.com',
'Cricket' => 'sms.mycricket.com',
'Nextel' => 'messaging.nextel.com',
'Sprint PCS' => 'messaging.sprintpcs.com',
'T-Mobile' => 'tmomail.net',
'Verizon' => 'vtext.com',
'Virgin Mobile' => 'vmobl.com' }), &:first)
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
end
Link to reset bounce counts for communication channels Fixes CNVS-20747 Test plan: - Create a user - Add an email address to the user - The address doesn't need to be able to actually receive messages, so put anything you want in here - Make note of the user's id - Assuming the user's id is 42, open a rails console and type: c = User.find(42).email_channel c.bounce_count = 3 c.save! - Log in as the user (actually log in, don't just masquerade) - Visit the user's settings page - Verify you see the usual warning triangle next to the user's email address - Verify you don't see the reset icon (just a refresh-like icon, two arrows pointing in a circle) - Log in as a siteadmin - Masquerade as the user - Visit the user's settings page - Verify you see both the warning triangle and the reset icon - Click the reset icon - Verify that the reset icon and the warning triangle go away - Refresh the page and verify that they're still gone - Assuming the user's id is 42, open a rails console and type: c = User.find(42).email_channel c.bounce_count = 1 c.save! - Log in as a siteadmin - Masquerade as the user - Visit the user's settings page - Verify you see only the reset icon and not the warning triangle - Click the reset icon - Verify that it goes away - Refresh the page and verify that it's still gone Change-Id: Ibd9d2e04555be2ec3eae811fd93f2cba0645d870 Reviewed-on: https://gerrit.instructure.com/55139 Reviewed-by: Joel Hough <joel@instructure.com> Tested-by: Jenkins QA-Review: Adrian Russell <arussell@instructure.com> Product-Review: Allison Weiss <allison@instructure.com>
2015-10-07 12:27:50 +08:00
set_policy do
given { |user| self.user.grants_right?(user, :manage_user_details) }
can :force_confirm
given { |user| Account.site_admin.grants_right?(user, :read_messages) }
can :reset_bounce_count
Expose bounce details via the API to siteadmins Fixes CNVS-22965 Test plan: - Create a user and a communication channel for that user, or reuse an existing one that has not been deleted - Bounce details: - From a console, do the following, where "..." is the id of the communication channel you're working with: c = CommunicationChannel.find(...) c.last_bounce_details = {'bouncedRecipients' => [{'diagnosticCode' => 'foo'}]} c.save! - As a siteadmin, hit /api/v1/users/.../communication_channels, where ... is the id of the user that owns the communication channel - Verify that the communication channel includes a "last_bounce_summary" property whose value is "foo" - Transient bounce details: - From a console, do the following, where "..." is the id of the communication channel you're working with: c = CommunicationChannel.find(...) c.last_transient_bounce_details = {'bouncedRecipients' => [{'diagnosticCode' => 'foo'}]} c.save! - As a siteadmin, hit /api/v1/users/.../communication_channels, where ... is the id of the user that owns the communication channel - Verify that the communication channel includes a "last_transient_bounce_summary" property whose value is "foo" - Bounce date: - From a console, do the following, where "..." is the id of the communication channel you're working with: c = CommunicationChannel.find(...) c.last_bounce_at = '2015-01-01T01:01:01.000Z' c.save! - As a siteadmin, hit /api/v1/users/.../communication_channels, where ... is the id of the user that owns the communication channel - Verify that the communication channel includes a "last_bounce_at" key whose values is "2015-01-01T01:01:01.000Z" - Transient bounce date: - From a console, do the following, where "..." is the id of the communication channel you're working with: c = CommunicationChannel.find(...) c.last_transient_bounce_at = '2015-01-01T01:01:01.000Z' c.save! - As a siteadmin, hit /api/v1/users/.../communication_channels, where ... is the id of the user that owns the communication channel - Verify that the communication channel includes a "last_transient_bounce_at" key whose values is "2015-01-01T01:01:01.000Z" - Suppression bounce date: - From a console, do the following, where "..." is the id of the communication channel you're working with: c = CommunicationChannel.find(...) c.last_suppression_bounce_at = '2015-01-01T01:01:01.000Z' c.save! - As a siteadmin, hit /api/v1/users/.../communication_channels, where ... is the id of the user that owns the communication channel - Verify that the communication channel includes a "last_suppression_bounce_at" key whose values is "2015-01-01T01:01:01.000Z" - Restricted to site admins: - As the user whose communication channel you were working with, hit /api/v1/users/.../communication_channels, where ... is that user's id - Verify that the communication channel does not include "last_bounce_at", "last_suppression_bounce_at", "last_transient_bounce_at", "last_bounce_summary", or "last_transient_bounce_summary" Change-Id: I10ed797620392229a55ffc797b2b35d3e979e19a Reviewed-on: https://gerrit.instructure.com/62578 Reviewed-by: Andrew Butterfield <abutterfield@instructure.com> Tested-by: Jenkins QA-Review: Adrian Russell <arussell@instructure.com> Product-Review: Allison Weiss <allison@instructure.com>
2015-09-04 07:06:44 +08:00
can :read_bounce_details
Link to reset bounce counts for communication channels Fixes CNVS-20747 Test plan: - Create a user - Add an email address to the user - The address doesn't need to be able to actually receive messages, so put anything you want in here - Make note of the user's id - Assuming the user's id is 42, open a rails console and type: c = User.find(42).email_channel c.bounce_count = 3 c.save! - Log in as the user (actually log in, don't just masquerade) - Visit the user's settings page - Verify you see the usual warning triangle next to the user's email address - Verify you don't see the reset icon (just a refresh-like icon, two arrows pointing in a circle) - Log in as a siteadmin - Masquerade as the user - Visit the user's settings page - Verify you see both the warning triangle and the reset icon - Click the reset icon - Verify that the reset icon and the warning triangle go away - Refresh the page and verify that they're still gone - Assuming the user's id is 42, open a rails console and type: c = User.find(42).email_channel c.bounce_count = 1 c.save! - Log in as a siteadmin - Masquerade as the user - Visit the user's settings page - Verify you see only the reset icon and not the warning triangle - Click the reset icon - Verify that it goes away - Refresh the page and verify that it's still gone Change-Id: Ibd9d2e04555be2ec3eae811fd93f2cba0645d870 Reviewed-on: https://gerrit.instructure.com/55139 Reviewed-by: Joel Hough <joel@instructure.com> Tested-by: Jenkins QA-Review: Adrian Russell <arussell@instructure.com> Product-Review: Allison Weiss <allison@instructure.com>
2015-10-07 12:27:50 +08:00
end
2011-02-01 09:57:29 +08:00
def pseudonym
user.pseudonyms.where(:unique_id => path).first if user
2011-02-01 09:57:29 +08:00
end
set_broadcast_policy do |p|
p.dispatch :forgot_password
p.to { self }
p.whenever { |record|
@request_password
}
user searching module refs #CNVS-2326 Added a UserSearch module that provides and interface for searching across the name, the email, and the sis id (and the database id), and added a trigram index on the columns that will be searched against. also made a slight refactor to the AR initializer in order to expose some of the behavior I wanted more granularly and added some specs to cover it fixed a small permissions bug in course.rb and pulled some scoping out of the courses controller down into the user search module TEST PLAN: This is currently not accessible from the site itself, this is just the ground work for the eventual user search api endpoint. The code is not called by anything currently in production either so there is no need for regression testing. The one thing to check would be the creation of new users (and pseudonyms and communication channels). There have been new indexes added to columns on those tables and there is some documentation indicating that writing to these indexes can be time consuming if the data set is quite large. It would be worth making sure that there have not been any unacceptable performance regressions in the creation of any of those record types in a database that has a full load of data (comparable to the production environment) Change-Id: I8fb13a6ec714f27efc8012c5ed2bed4d963c24e6 Reviewed-on: https://gerrit.instructure.com/16459 Tested-by: Jenkins <jenkins@instructure.com> QA-Review: Clare Hetherington <clare@instructure.com> Reviewed-by: Brian Palmer <brianp@instructure.com>
2012-12-29 03:28:09 +08:00
2011-02-01 09:57:29 +08:00
p.dispatch :confirm_registration
p.to { self }
p.whenever { |record|
@send_confirmation and
(record.workflow_state == 'active' ||
2011-02-01 09:57:29 +08:00
(record.workflow_state == 'unconfirmed' and (self.user.pre_registered? || self.user.creation_pending?))) and
self.path_type == TYPE_EMAIL
2011-02-01 09:57:29 +08:00
}
p.dispatch :confirm_email_communication_channel
p.to { self }
p.whenever { |record|
2011-02-01 09:57:29 +08:00
@send_confirmation and
record.workflow_state == 'unconfirmed' and self.user.registered? and
self.path_type == TYPE_EMAIL
2011-02-01 09:57:29 +08:00
}
p.context { @root_account }
user searching module refs #CNVS-2326 Added a UserSearch module that provides and interface for searching across the name, the email, and the sis id (and the database id), and added a trigram index on the columns that will be searched against. also made a slight refactor to the AR initializer in order to expose some of the behavior I wanted more granularly and added some specs to cover it fixed a small permissions bug in course.rb and pulled some scoping out of the courses controller down into the user search module TEST PLAN: This is currently not accessible from the site itself, this is just the ground work for the eventual user search api endpoint. The code is not called by anything currently in production either so there is no need for regression testing. The one thing to check would be the creation of new users (and pseudonyms and communication channels). There have been new indexes added to columns on those tables and there is some documentation indicating that writing to these indexes can be time consuming if the data set is quite large. It would be worth making sure that there have not been any unacceptable performance regressions in the creation of any of those record types in a database that has a full load of data (comparable to the production environment) Change-Id: I8fb13a6ec714f27efc8012c5ed2bed4d963c24e6 Reviewed-on: https://gerrit.instructure.com/16459 Tested-by: Jenkins <jenkins@instructure.com> QA-Review: Clare Hetherington <clare@instructure.com> Reviewed-by: Brian Palmer <brianp@instructure.com>
2012-12-29 03:28:09 +08:00
2011-02-01 09:57:29 +08:00
p.dispatch :merge_email_communication_channel
p.to { self }
p.whenever {|record|
@send_merge_notification and
self.path_type == TYPE_EMAIL
2011-02-01 09:57:29 +08:00
}
user searching module refs #CNVS-2326 Added a UserSearch module that provides and interface for searching across the name, the email, and the sis id (and the database id), and added a trigram index on the columns that will be searched against. also made a slight refactor to the AR initializer in order to expose some of the behavior I wanted more granularly and added some specs to cover it fixed a small permissions bug in course.rb and pulled some scoping out of the courses controller down into the user search module TEST PLAN: This is currently not accessible from the site itself, this is just the ground work for the eventual user search api endpoint. The code is not called by anything currently in production either so there is no need for regression testing. The one thing to check would be the creation of new users (and pseudonyms and communication channels). There have been new indexes added to columns on those tables and there is some documentation indicating that writing to these indexes can be time consuming if the data set is quite large. It would be worth making sure that there have not been any unacceptable performance regressions in the creation of any of those record types in a database that has a full load of data (comparable to the production environment) Change-Id: I8fb13a6ec714f27efc8012c5ed2bed4d963c24e6 Reviewed-on: https://gerrit.instructure.com/16459 Tested-by: Jenkins <jenkins@instructure.com> QA-Review: Clare Hetherington <clare@instructure.com> Reviewed-by: Brian Palmer <brianp@instructure.com>
2012-12-29 03:28:09 +08:00
2011-02-01 09:57:29 +08:00
p.dispatch :confirm_sms_communication_channel
p.to { self }
p.whenever { |record|
@send_confirmation and
record.workflow_state == 'unconfirmed' and
self.path_type == TYPE_SMS and
2011-02-01 09:57:29 +08:00
!self.user.creation_pending?
}
p.context { @root_account }
2011-02-01 09:57:29 +08:00
end
def uniqueness_of_path
return if path.nil?
return if retired?
return unless user_id
scope = self.class.by_path(path).where(user_id: user_id, path_type: path_type, workflow_state: ['unconfirmed', 'active'])
unless new_record?
scope = scope.where("id<>?", id)
end
if scope.exists?
self.errors.add(:path, :taken, :value => path)
end
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 not_otp_communication_channel
self.errors.add(:workflow_state, "Can't remove a user's SMS that is used for one time passwords") if self.id == self.user.otp_communication_channel_id
end
# Public: Build the url where this record can be confirmed.
#
#
# Returns a string.
def confirmation_url
return "" unless path_type == TYPE_EMAIL
"#{HostUrl.protocol}://#{HostUrl.context_host(context)}/register/#{confirmation_code}"
end
def context
pseudonym.try(:account)
end
# Public: Determine if this channel is the product of an SIS import.
#
# Returns a boolean.
def imported?
id.present? &&
Pseudonym.where(:sis_communication_channel_id => self).exists?
end
# Return the 'path' for simple communication channel types like email and sms. For
# Yo and Twitter, return the user's configured user_name for the service.
2011-02-01 09:57:29 +08:00
def path_description
if self.path_type == TYPE_TWITTER
res = self.user.user_services.for_service(TYPE_TWITTER).first.service_user_name rescue nil
res ||= t :default_twitter_handle, 'Twitter Handle'
res
elsif self.path_type == TYPE_YO
res = self.user.user_services.for_service(TYPE_YO).first.service_user_name rescue nil
res ||= t :default_yo_name, 'Yo Name'
res
elsif self.path_type == TYPE_PUSH
t 'For All Devices'
2011-02-01 09:57:29 +08:00
else
self.path
end
end
2011-02-01 09:57:29 +08:00
def forgot_password!
@request_password = true
set_confirmation_code(true)
self.save!
@request_password = false
end
def send_confirmation!(root_account)
2011-02-01 09:57:29 +08:00
@send_confirmation = true
@root_account = root_account
2011-02-01 09:57:29 +08:00
self.save!
@root_account = nil
2011-02-01 09:57:29 +08:00
@send_confirmation = false
end
2011-02-01 09:57:29 +08:00
def send_merge_notification!
@send_merge_notification = true
self.save!
@send_merge_notification = false
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 send_otp!(code)
m = self.messages.scoped.new
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
m.to = self.path
m.body = t :body, "Your Canvas verification code is %{verification_code}", :verification_code => code
Mailer.create_message(m).deliver rescue nil # omg! just ignore delivery failures
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
end
2011-02-01 09:57:29 +08:00
# If you are creating a new communication_channel, do nothing, this just
# works. If you are resetting the confirmation_code, call @cc.
# set_confirmation_code(true), or just save the record to leave the old
# confirmation code in place.
2011-02-01 09:57:29 +08:00
def set_confirmation_code(reset=false)
self.confirmation_code = nil if reset
if self.path_type == TYPE_EMAIL or self.path_type.nil?
self.confirmation_code ||= CanvasSlug.generate(nil, 25)
2011-02-01 09:57:29 +08:00
else
self.confirmation_code ||= CanvasSlug.generate
2011-02-01 09:57:29 +08:00
end
true
2011-02-01 09:57:29 +08:00
end
scope :for, lambda { |context|
2011-02-01 09:57:29 +08:00
case context
when User
where(:user_id => context)
2011-02-01 09:57:29 +08:00
when Notification
eager_load(:notification_policies).where(:notification_policies => { :notification_id => context })
2011-02-01 09:57:29 +08:00
else
scoped
2011-02-01 09:57:29 +08:00
end
}
def self.by_path_condition(path)
if %{mysql mysql2}.include?(connection_pool.spec.config[:adapter])
path
else
"LOWER(#{path})"
end
end
scope :by_path, lambda { |path|
where("#{by_path_condition("communication_channels.path")}=#{by_path_condition("?")}", path)
}
scope :email, -> { where(:path_type => TYPE_EMAIL) }
scope :sms, -> { where(:path_type => TYPE_SMS) }
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
scope :active, -> { where(:workflow_state => 'active') }
scope :unretired, -> { where("communication_channels.workflow_state<>'retired'") }
scope :for_notification_frequency, lambda { |notification, frequency|
joins(:notification_policies).where(:notification_policies => { :notification_id => notification, :frequency => frequency })
2011-02-01 09:57:29 +08:00
}
# Get the list of communication channels that overrides an association's default order clause.
# This returns an unretired and properly ordered already fetch array of CommunicationChannel objects ready for usage.
def self.all_ordered_for_display(user)
# Add communication channel for users that already had Twitter
# integrated before we started offering it as a cc
twitter_service = user.user_services.for_service(CommunicationChannel::TYPE_TWITTER).first
twitter_service.assert_communication_channel if twitter_service
rank_order = [TYPE_EMAIL, TYPE_SMS, TYPE_PUSH]
# Add twitter and yo (in that order) if the user's account is setup for them.
rank_order << TYPE_TWITTER if twitter_service
rank_order << TYPE_YO unless user.user_services.for_service(CommunicationChannel::TYPE_YO).empty?
self.unretired.where('communication_channels.path_type IN (?)', rank_order).
order("#{self.rank_sql(rank_order, 'communication_channels.path_type')} ASC, communication_channels.position asc").to_a
end
scope :include_policies, -> { preload(:notification_policies) }
scope :in_state, lambda { |state| where(:workflow_state => state.to_s) }
scope :of_type, lambda { |type| where(:path_type => type) }
2011-02-01 09:57:29 +08:00
def can_notify?
self.notification_policies.any? { |np| np.frequency == 'never' } ? false : true
end
2011-02-01 09:57:29 +08:00
def move_to_user(user, migrate=true)
return unless user
if self.pseudonym && self.pseudonym.unique_id == self.path
self.pseudonym.move_to_user(user, migrate)
else
old_user_id = self.user_id
self.user_id = user.id
self.save!
if old_user_id
Pseudonym.where(:user_id => old_user_id, :unique_id => self.path).update_all(:user_id => user)
User.where(:id => [old_user_id, user]).touch_all
2011-02-01 09:57:29 +08:00
end
end
end
2011-02-01 09:57:29 +08:00
def consider_building_pseudonym
if self.build_pseudonym_on_confirm && self.active?
self.build_pseudonym_on_confirm = false
pseudonym = self.user.pseudonyms.build(:unique_id => self.path, :account => Account.default)
existing_pseudonym = self.user.pseudonyms.active.find{|p| p.account_id == Account.default.id }
2011-02-01 09:57:29 +08:00
if existing_pseudonym
pseudonym.password_salt = existing_pseudonym.password_salt
pseudonym.crypted_password = existing_pseudonym.crypted_password
end
pseudonym.save!
end
true
end
2011-02-01 09:57:29 +08:00
alias_method :destroy!, :destroy
def destroy
self.workflow_state = 'retired'
self.save
end
2011-02-01 09:57:29 +08:00
workflow do
state :unconfirmed do
event :confirm, :transitions_to => :active do
self.set_confirmation_code
2011-02-01 09:57:29 +08:00
end
event :retire, :transitions_to => :retired
end
2011-02-01 09:57:29 +08:00
state :active do
event :retire, :transitions_to => :retired
end
2011-02-01 09:57:29 +08:00
state :retired do
event :re_activate, :transitions_to => :active do
# Reset bounce count when we're being reactivated
reset_bounce_count!
end
2011-02-01 09:57:29 +08:00
end
end
# This is setup as a default in the database, but this overcomes misspellings.
def assert_path_type
valid_types = [TYPE_EMAIL, TYPE_SMS, TYPE_TWITTER, TYPE_PUSH, TYPE_YO]
self.path_type = TYPE_EMAIL unless valid_types.include?(path_type)
true
2011-02-01 09:57:29 +08:00
end
protected :assert_path_type
2011-02-01 09:57:29 +08:00
def self.serialization_excludes; [:confirmation_code]; end
def self.associated_shards(path)
[Shard.default]
end
def merge_candidates(break_on_first_found = false)
return [] if path_type == 'push'
shards = self.class.associated_shards(self.path) if Enrollment.cross_shard_invitations?
shards ||= [self.shard]
scope = CommunicationChannel.active.by_path(self.path).of_type(self.path_type)
merge_candidates = {}
Shard.with_each_shard(shards) do
scope = scope.shard(Shard.current)
scope.where("user_id<>?", self.user_id).preload(:user).map(&:user).select do |u|
result = merge_candidates.fetch(u.global_id) do
merge_candidates[u.global_id] = (u.all_active_pseudonyms.length != 0)
end
return [u] if result && break_on_first_found
result
end
end.uniq
end
def has_merge_candidates?
!merge_candidates(true).empty?
end
def bouncing?
self.bounce_count >= RETIRE_THRESHOLD
end
def was_bouncing?
old_bounce_count = self.previous_changes[:bounce_count].try(:first)
old_bounce_count ||= self.bounce_count
old_bounce_count >= RETIRE_THRESHOLD
end
def reset_bounce_count!
self.bounce_count = 0
self.save!
end
def was_retired?
old_workflow_state = self.previous_changes[:workflow_state].try(:first)
old_workflow_state ||= self.workflow_state
old_workflow_state.to_s == 'retired'
end
def check_if_bouncing_changed
if retired?
self.user.update_bouncing_channel_message!(self) if !was_retired? && was_bouncing?
else
if (was_retired? && bouncing?) || (was_bouncing? != bouncing?)
self.user.update_bouncing_channel_message!(self)
end
end
end
private :check_if_bouncing_changed
def self.bounce_for_path(path:, timestamp:, details:, permanent_bounce:, suppression_bounce:)
Shard.with_each_shard(CommunicationChannel.associated_shards(path)) do
CommunicationChannel.unretired.email.by_path(path).each do |channel|
channel.bounce_count = channel.bounce_count + 1 if permanent_bounce
Store information about the last bounce for a communication channel Fixes CNVS-20894 Test plan: - Set up outgoing email through the Amazon SES test account - Set up bounce_notifications.yml with the SQS test creds - Set a user's email address to bounce@simulator.amazonses.com - Cause a message to be sent to that user - Wait for the bounce notification processor to run (~5 minutes) - At a Rails console, check the CommunicationChannel's last_bounce_at and verify that it roughly corresponds to when the initial message was sent - Check the CommunicationChannel's last_bounce_details and ensure that they have been filled in - Cause another message to be sent to the user and wait for the bounce notification processor to run again - Check the CommunicationChannel's last_bounce_at and ensure that it has changed - Check the CommunicationChannel's last_bounce_details and ensure that they have also changed - Change the CommunicationChannel's path to be suppressionlist@simulator.amazonses.com - Cause another message to be sent to the user and wait for the bounce notification processor to run again - Check the CommunicationChannel's last_bounce_at and ensure that it has _not_ changed - Check the CommunicationChannel's last_suppression_bounce_at and ensure that it is now filled in - Check the CommunicationChannel's last_bounce_details and ensure that they have _not_ changed - Set another user's email address to success@simulator.amazonses.com - Cause a message to be sent for that user and ensure that it is sent successfully Change-Id: I771f59a5e9b33d415dd38c3d4a80f65fc1e003bd Reviewed-on: https://gerrit.instructure.com/55660 Reviewed-by: Jacob Fugal <jacob@instructure.com> Reviewed-by: Andrew Butterfield <abutterfield@instructure.com> Tested-by: Jenkins QA-Review: Heath Hales <hhales@instructure.com> Product-Review: Peyton Craighill <pcraighill@instructure.com>
2015-06-04 05:08:30 +08:00
if suppression_bounce
channel.last_suppression_bounce_at = timestamp
elsif permanent_bounce
Store information about the last bounce for a communication channel Fixes CNVS-20894 Test plan: - Set up outgoing email through the Amazon SES test account - Set up bounce_notifications.yml with the SQS test creds - Set a user's email address to bounce@simulator.amazonses.com - Cause a message to be sent to that user - Wait for the bounce notification processor to run (~5 minutes) - At a Rails console, check the CommunicationChannel's last_bounce_at and verify that it roughly corresponds to when the initial message was sent - Check the CommunicationChannel's last_bounce_details and ensure that they have been filled in - Cause another message to be sent to the user and wait for the bounce notification processor to run again - Check the CommunicationChannel's last_bounce_at and ensure that it has changed - Check the CommunicationChannel's last_bounce_details and ensure that they have also changed - Change the CommunicationChannel's path to be suppressionlist@simulator.amazonses.com - Cause another message to be sent to the user and wait for the bounce notification processor to run again - Check the CommunicationChannel's last_bounce_at and ensure that it has _not_ changed - Check the CommunicationChannel's last_suppression_bounce_at and ensure that it is now filled in - Check the CommunicationChannel's last_bounce_details and ensure that they have _not_ changed - Set another user's email address to success@simulator.amazonses.com - Cause a message to be sent for that user and ensure that it is sent successfully Change-Id: I771f59a5e9b33d415dd38c3d4a80f65fc1e003bd Reviewed-on: https://gerrit.instructure.com/55660 Reviewed-by: Jacob Fugal <jacob@instructure.com> Reviewed-by: Andrew Butterfield <abutterfield@instructure.com> Tested-by: Jenkins QA-Review: Heath Hales <hhales@instructure.com> Product-Review: Peyton Craighill <pcraighill@instructure.com>
2015-06-04 05:08:30 +08:00
channel.last_bounce_at = timestamp
channel.last_bounce_details = details
else
channel.last_transient_bounce_at = timestamp
channel.last_transient_bounce_details = details
Store information about the last bounce for a communication channel Fixes CNVS-20894 Test plan: - Set up outgoing email through the Amazon SES test account - Set up bounce_notifications.yml with the SQS test creds - Set a user's email address to bounce@simulator.amazonses.com - Cause a message to be sent to that user - Wait for the bounce notification processor to run (~5 minutes) - At a Rails console, check the CommunicationChannel's last_bounce_at and verify that it roughly corresponds to when the initial message was sent - Check the CommunicationChannel's last_bounce_details and ensure that they have been filled in - Cause another message to be sent to the user and wait for the bounce notification processor to run again - Check the CommunicationChannel's last_bounce_at and ensure that it has changed - Check the CommunicationChannel's last_bounce_details and ensure that they have also changed - Change the CommunicationChannel's path to be suppressionlist@simulator.amazonses.com - Cause another message to be sent to the user and wait for the bounce notification processor to run again - Check the CommunicationChannel's last_bounce_at and ensure that it has _not_ changed - Check the CommunicationChannel's last_suppression_bounce_at and ensure that it is now filled in - Check the CommunicationChannel's last_bounce_details and ensure that they have _not_ changed - Set another user's email address to success@simulator.amazonses.com - Cause a message to be sent for that user and ensure that it is sent successfully Change-Id: I771f59a5e9b33d415dd38c3d4a80f65fc1e003bd Reviewed-on: https://gerrit.instructure.com/55660 Reviewed-by: Jacob Fugal <jacob@instructure.com> Reviewed-by: Andrew Butterfield <abutterfield@instructure.com> Tested-by: Jenkins QA-Review: Heath Hales <hhales@instructure.com> Product-Review: Peyton Craighill <pcraighill@instructure.com>
2015-06-04 05:08:30 +08:00
end
Store information about the last bounce for a communication channel Fixes CNVS-20894 Test plan: - Set up outgoing email through the Amazon SES test account - Set up bounce_notifications.yml with the SQS test creds - Set a user's email address to bounce@simulator.amazonses.com - Cause a message to be sent to that user - Wait for the bounce notification processor to run (~5 minutes) - At a Rails console, check the CommunicationChannel's last_bounce_at and verify that it roughly corresponds to when the initial message was sent - Check the CommunicationChannel's last_bounce_details and ensure that they have been filled in - Cause another message to be sent to the user and wait for the bounce notification processor to run again - Check the CommunicationChannel's last_bounce_at and ensure that it has changed - Check the CommunicationChannel's last_bounce_details and ensure that they have also changed - Change the CommunicationChannel's path to be suppressionlist@simulator.amazonses.com - Cause another message to be sent to the user and wait for the bounce notification processor to run again - Check the CommunicationChannel's last_bounce_at and ensure that it has _not_ changed - Check the CommunicationChannel's last_suppression_bounce_at and ensure that it is now filled in - Check the CommunicationChannel's last_bounce_details and ensure that they have _not_ changed - Set another user's email address to success@simulator.amazonses.com - Cause a message to be sent for that user and ensure that it is sent successfully Change-Id: I771f59a5e9b33d415dd38c3d4a80f65fc1e003bd Reviewed-on: https://gerrit.instructure.com/55660 Reviewed-by: Jacob Fugal <jacob@instructure.com> Reviewed-by: Andrew Butterfield <abutterfield@instructure.com> Tested-by: Jenkins QA-Review: Heath Hales <hhales@instructure.com> Product-Review: Peyton Craighill <pcraighill@instructure.com>
2015-06-04 05:08:30 +08:00
channel.save!
end
end
end
Expose bounce details via the API to siteadmins Fixes CNVS-22965 Test plan: - Create a user and a communication channel for that user, or reuse an existing one that has not been deleted - Bounce details: - From a console, do the following, where "..." is the id of the communication channel you're working with: c = CommunicationChannel.find(...) c.last_bounce_details = {'bouncedRecipients' => [{'diagnosticCode' => 'foo'}]} c.save! - As a siteadmin, hit /api/v1/users/.../communication_channels, where ... is the id of the user that owns the communication channel - Verify that the communication channel includes a "last_bounce_summary" property whose value is "foo" - Transient bounce details: - From a console, do the following, where "..." is the id of the communication channel you're working with: c = CommunicationChannel.find(...) c.last_transient_bounce_details = {'bouncedRecipients' => [{'diagnosticCode' => 'foo'}]} c.save! - As a siteadmin, hit /api/v1/users/.../communication_channels, where ... is the id of the user that owns the communication channel - Verify that the communication channel includes a "last_transient_bounce_summary" property whose value is "foo" - Bounce date: - From a console, do the following, where "..." is the id of the communication channel you're working with: c = CommunicationChannel.find(...) c.last_bounce_at = '2015-01-01T01:01:01.000Z' c.save! - As a siteadmin, hit /api/v1/users/.../communication_channels, where ... is the id of the user that owns the communication channel - Verify that the communication channel includes a "last_bounce_at" key whose values is "2015-01-01T01:01:01.000Z" - Transient bounce date: - From a console, do the following, where "..." is the id of the communication channel you're working with: c = CommunicationChannel.find(...) c.last_transient_bounce_at = '2015-01-01T01:01:01.000Z' c.save! - As a siteadmin, hit /api/v1/users/.../communication_channels, where ... is the id of the user that owns the communication channel - Verify that the communication channel includes a "last_transient_bounce_at" key whose values is "2015-01-01T01:01:01.000Z" - Suppression bounce date: - From a console, do the following, where "..." is the id of the communication channel you're working with: c = CommunicationChannel.find(...) c.last_suppression_bounce_at = '2015-01-01T01:01:01.000Z' c.save! - As a siteadmin, hit /api/v1/users/.../communication_channels, where ... is the id of the user that owns the communication channel - Verify that the communication channel includes a "last_suppression_bounce_at" key whose values is "2015-01-01T01:01:01.000Z" - Restricted to site admins: - As the user whose communication channel you were working with, hit /api/v1/users/.../communication_channels, where ... is that user's id - Verify that the communication channel does not include "last_bounce_at", "last_suppression_bounce_at", "last_transient_bounce_at", "last_bounce_summary", or "last_transient_bounce_summary" Change-Id: I10ed797620392229a55ffc797b2b35d3e979e19a Reviewed-on: https://gerrit.instructure.com/62578 Reviewed-by: Andrew Butterfield <abutterfield@instructure.com> Tested-by: Jenkins QA-Review: Adrian Russell <arussell@instructure.com> Product-Review: Allison Weiss <allison@instructure.com>
2015-09-04 07:06:44 +08:00
def last_bounce_summary
last_bounce_details.try(:[], 'bouncedRecipients').try(:[], 0).try(:[], 'diagnosticCode')
end
def last_transient_bounce_summary
last_transient_bounce_details.try(:[], 'bouncedRecipients').try(:[], 0).try(:[], 'diagnosticCode')
end
def self.find_by_confirmation_code(code)
where(confirmation_code: code).first
end
2011-02-01 09:57:29 +08:00
end