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
|
2012-01-14 13:05:07 +08:00
|
|
|
extend ActiveSupport::Memoizable
|
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
# You should start thinking about communication channels
|
|
|
|
# as independent of pseudonyms
|
|
|
|
include Workflow
|
|
|
|
|
|
|
|
attr_accessible :user, :path, :path_type, :build_pseudonym_on_confirm, :pseudonym
|
|
|
|
|
|
|
|
belongs_to :pseudonym
|
|
|
|
has_many :pseudonyms
|
|
|
|
belongs_to :user
|
|
|
|
has_many :notification_policies, :dependent => :destroy
|
|
|
|
has_many :messages
|
2013-10-15 03:28:32 +08:00
|
|
|
belongs_to :access_token
|
2012-12-29 03:28:09 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
before_save :consider_retiring, :assert_path_type, :set_confirmation_code
|
|
|
|
before_save :consider_building_pseudonym
|
2013-08-08 06:19:48 +08:00
|
|
|
validates_presence_of :path, :path_type, :user, :workflow_state
|
2012-01-06 02:57:35 +08:00
|
|
|
validate :uniqueness_of_path
|
2012-11-14 04:21:18 +08:00
|
|
|
validate :not_otp_communication_channel, :if => lambda { |cc| cc.path_type == TYPE_SMS && cc.retired? && !cc.new_record? }
|
2013-10-15 03:28:32 +08:00
|
|
|
validates_presence_of :access_token_id, if: lambda { |cc| cc.path_type == TYPE_PUSH }
|
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
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
acts_as_list :scope => :user_id
|
2012-12-29 03:28:09 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
has_a_broadcast_policy
|
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
|
|
|
|
2012-07-19 07:18:29 +08:00
|
|
|
# Constants for the different supported communication channels
|
|
|
|
TYPE_EMAIL = 'email'
|
|
|
|
TYPE_SMS = 'sms'
|
|
|
|
TYPE_CHAT = 'chat'
|
|
|
|
TYPE_TWITTER = 'twitter'
|
|
|
|
TYPE_FACEBOOK = 'facebook'
|
2013-10-15 03:28:32 +08:00
|
|
|
TYPE_PUSH = 'push'
|
2012-07-19 07:18:29 +08:00
|
|
|
|
2013-06-01 00:51:54 +08:00
|
|
|
RETIRE_THRESHOLD = 5
|
|
|
|
|
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
|
2013-09-27 05:15:55 +08:00
|
|
|
@sms_carriers ||= Canvas::ICU.collate_by((Setting.from_config('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',
|
2013-01-05 05:59:44 +08:00
|
|
|
'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',
|
2013-09-27 05:15:55 +08:00
|
|
|
'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
|
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def pseudonym
|
2013-03-19 03:07:47 +08:00
|
|
|
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
|
|
|
|
}
|
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' ||
|
|
|
|
(record.workflow_state == 'unconfirmed' and (self.user.pre_registered? || self.user.creation_pending?))) and
|
2012-07-19 07:18:29 +08:00
|
|
|
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|
|
|
|
|
@send_confirmation and
|
|
|
|
record.workflow_state == 'unconfirmed' and self.user.registered? and
|
2012-07-19 07:18:29 +08:00
|
|
|
self.path_type == TYPE_EMAIL
|
2011-02-01 09:57:29 +08:00
|
|
|
}
|
2012-02-21 04:33:49 +08:00
|
|
|
p.context { @root_account }
|
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
|
2012-07-19 07:18:29 +08:00
|
|
|
self.path_type == TYPE_EMAIL
|
2011-02-01 09:57:29 +08:00
|
|
|
}
|
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
|
2012-07-19 07:18:29 +08:00
|
|
|
self.path_type == TYPE_SMS and
|
2011-02-01 09:57:29 +08:00
|
|
|
!self.user.creation_pending?
|
|
|
|
}
|
2012-02-21 04:33:49 +08:00
|
|
|
p.context { @root_account }
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
2012-02-21 04:33:49 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def active_pseudonyms
|
|
|
|
self.user.pseudonyms.active
|
|
|
|
end
|
|
|
|
memoize :active_pseudonyms
|
2012-01-06 02:57:35 +08:00
|
|
|
|
|
|
|
def uniqueness_of_path
|
|
|
|
return if path.nil?
|
|
|
|
return if retired?
|
|
|
|
return unless user_id
|
2013-07-19 02:19:35 +08:00
|
|
|
conditions = ["LOWER(path)=LOWER(?) AND user_id=? AND path_type=? AND workflow_state IN('unconfirmed', 'active')", path, user_id, path_type]
|
2012-01-06 02:57:35 +08:00
|
|
|
unless new_record?
|
|
|
|
conditions.first << " AND id<>?"
|
|
|
|
conditions << id
|
|
|
|
end
|
2013-03-19 03:07:47 +08:00
|
|
|
if self.class.where(conditions).exists?
|
2012-01-06 02:57:35 +08:00
|
|
|
self.errors.add(:path, :taken, :value => path)
|
|
|
|
end
|
|
|
|
end
|
2012-07-19 07:18:29 +08:00
|
|
|
|
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
|
|
|
|
|
2012-10-03 04:17:54 +08:00
|
|
|
def context
|
|
|
|
pseudonym.try(:account)
|
|
|
|
end
|
|
|
|
|
2013-02-26 05:32:41 +08:00
|
|
|
# Public: Determine if this channel is the product of an SIS import.
|
|
|
|
#
|
|
|
|
# Returns a boolean.
|
|
|
|
def imported?
|
|
|
|
id.present? &&
|
2013-03-19 03:07:47 +08:00
|
|
|
Pseudonym.where(:sis_communication_channel_id => self).exists?
|
2013-02-26 05:32:41 +08:00
|
|
|
end
|
|
|
|
|
2012-07-19 07:18:29 +08:00
|
|
|
# Return the 'path' for simple communication channel types like email and sms. For
|
|
|
|
# Facebook and Twitter, return the user's configured user_name for the service.
|
2011-02-01 09:57:29 +08:00
|
|
|
def path_description
|
2012-07-19 07:18:29 +08:00
|
|
|
if self.path_type == TYPE_FACEBOOK
|
|
|
|
res = self.user.user_services.for_service(TYPE_FACEBOOK).first.service_user_name rescue nil
|
|
|
|
res ||= t :default_facebook_account, 'Facebook Account'
|
2011-02-01 09:57:29 +08:00
|
|
|
res
|
2012-07-19 07:18:29 +08:00
|
|
|
elsif 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'
|
2011-02-10 07:50:14 +08:00
|
|
|
res
|
2013-10-15 03:28:32 +08:00
|
|
|
elsif self.path_type == TYPE_PUSH
|
|
|
|
access_token.purpose ? "#{access_token.purpose} (#{access_token.developer_key.name})" : access_token.developer_key.name
|
2011-02-01 09:57:29 +08:00
|
|
|
else
|
|
|
|
self.path
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def forgot_password!
|
|
|
|
@request_password = true
|
|
|
|
set_confirmation_code(true)
|
|
|
|
self.save!
|
|
|
|
@request_password = false
|
|
|
|
end
|
|
|
|
|
2012-02-21 04:33:49 +08:00
|
|
|
def send_confirmation!(root_account)
|
2011-02-01 09:57:29 +08:00
|
|
|
@send_confirmation = true
|
2012-02-21 04:33:49 +08:00
|
|
|
@root_account = root_account
|
2011-02-01 09:57:29 +08:00
|
|
|
self.save!
|
2012-02-21 04:33:49 +08:00
|
|
|
@root_account = nil
|
2011-02-01 09:57:29 +08:00
|
|
|
@send_confirmation = false
|
|
|
|
end
|
|
|
|
|
|
|
|
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.new
|
|
|
|
m.to = self.path
|
|
|
|
m.body = t :body, "Your Canvas verification code is %{verification_code}", :verification_code => code
|
2013-10-05 05:14:44 +08:00
|
|
|
Mailer.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.
|
|
|
|
def set_confirmation_code(reset=false)
|
|
|
|
self.confirmation_code = nil if reset
|
2012-07-19 07:18:29 +08:00
|
|
|
if self.path_type == TYPE_EMAIL or self.path_type.nil?
|
2011-02-01 09:57:29 +08:00
|
|
|
self.confirmation_code ||= AutoHandle.generate(nil, 25)
|
|
|
|
else
|
|
|
|
self.confirmation_code ||= AutoHandle.generate
|
|
|
|
end
|
2013-06-01 00:51:54 +08:00
|
|
|
true
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
|
|
|
|
2013-03-21 03:38:19 +08:00
|
|
|
scope :for, lambda { |context|
|
2011-02-01 09:57:29 +08:00
|
|
|
case context
|
|
|
|
when User
|
2013-03-21 03:38:19 +08:00
|
|
|
where(:user_id => context)
|
2011-02-01 09:57:29 +08:00
|
|
|
when Notification
|
2013-03-21 03:38:19 +08:00
|
|
|
includes(:notification_policies).where(:notification_policies => { :notification_id => context })
|
2011-02-01 09:57:29 +08:00
|
|
|
else
|
2013-03-21 03:38:19 +08:00
|
|
|
scoped
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
|
|
|
}
|
2011-12-31 07:10:04 +08:00
|
|
|
|
2013-03-21 03:38:19 +08:00
|
|
|
scope :by_path, lambda { |path|
|
2013-02-27 01:37:54 +08:00
|
|
|
if %{mysql mysql2}.include?(connection_pool.spec.config[:adapter])
|
2013-03-21 03:38:19 +08:00
|
|
|
where(:path => path)
|
2011-12-31 07:10:04 +08:00
|
|
|
else
|
2013-07-19 02:19:35 +08:00
|
|
|
where("LOWER(communication_channels.path)=LOWER(?)", path)
|
2011-12-31 07:10:04 +08:00
|
|
|
end
|
|
|
|
}
|
|
|
|
|
2013-03-21 03:38:19 +08:00
|
|
|
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
|
|
|
|
2013-03-21 03:38:19 +08:00
|
|
|
scope :active, where(:workflow_state => 'active')
|
|
|
|
scope :unretired, where("communication_channels.workflow_state<>'retired'")
|
2011-11-03 04:59:41 +08:00
|
|
|
|
2013-03-21 03:38:19 +08:00
|
|
|
scope :for_notification_frequency, lambda { |notification, frequency|
|
|
|
|
includes(:notification_policies).where(:notification_policies => { :notification_id => notification, :frequency => frequency })
|
2011-02-01 09:57:29 +08:00
|
|
|
}
|
2012-07-19 07:18: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]
|
|
|
|
# Add facebook and twitter (in that order) if the user's account is setup for them.
|
|
|
|
rank_order << TYPE_FACEBOOK unless user.user_services.for_service(CommunicationChannel::TYPE_FACEBOOK).empty?
|
|
|
|
rank_order << TYPE_TWITTER if twitter_service
|
2013-03-19 03:07:47 +08:00
|
|
|
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").
|
|
|
|
all
|
2012-07-19 07:18:29 +08:00
|
|
|
end
|
|
|
|
|
2013-03-21 03:38:19 +08:00
|
|
|
scope :include_policies, includes(:notification_policies)
|
2011-11-03 04:59:41 +08:00
|
|
|
|
2013-03-21 03:38:19 +08:00
|
|
|
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
|
|
|
|
|
|
|
|
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
|
2013-03-19 03:07:47 +08:00
|
|
|
Pseudonym.where(:user_id => old_user_id, :unique_id => self.path).update_all(:user_id => user)
|
|
|
|
User.where(:id => [old_user_id, user]).update_all(:updated_at => Time.now.utc)
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
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.select{|p| p.account_id == Account.default.id }.first
|
|
|
|
if existing_pseudonym
|
|
|
|
pseudonym.password_salt = existing_pseudonym.password_salt
|
|
|
|
pseudonym.crypted_password = existing_pseudonym.crypted_password
|
|
|
|
end
|
|
|
|
pseudonym.save!
|
|
|
|
end
|
|
|
|
true
|
|
|
|
end
|
|
|
|
|
|
|
|
def consider_retiring
|
2013-06-01 00:51:54 +08:00
|
|
|
self.retire if self.bounce_count >= RETIRE_THRESHOLD
|
|
|
|
true
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
alias_method :destroy!, :destroy
|
|
|
|
def destroy
|
|
|
|
self.workflow_state = 'retired'
|
|
|
|
self.save
|
|
|
|
end
|
|
|
|
|
|
|
|
workflow do
|
|
|
|
state :unconfirmed do
|
|
|
|
event :confirm, :transitions_to => :active do
|
|
|
|
self.set_confirmation_code(true)
|
|
|
|
end
|
|
|
|
event :retire, :transitions_to => :retired
|
|
|
|
end
|
|
|
|
|
|
|
|
state :active do
|
|
|
|
event :retire, :transitions_to => :retired
|
|
|
|
end
|
|
|
|
|
|
|
|
state :retired do
|
|
|
|
event :re_activate, :transitions_to => :active do
|
|
|
|
self.bounce_count = 0
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# This is setup as a default in the database, but this overcomes misspellings.
|
|
|
|
def assert_path_type
|
|
|
|
pt = self.path_type
|
2013-10-15 03:28:32 +08:00
|
|
|
self.path_type = TYPE_EMAIL unless pt == TYPE_EMAIL or pt == TYPE_SMS or pt == TYPE_CHAT or pt == TYPE_FACEBOOK or pt == TYPE_TWITTER or pt == TYPE_PUSH
|
2013-06-01 00:51:54 +08:00
|
|
|
true
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
|
|
|
protected :assert_path_type
|
|
|
|
|
|
|
|
def self.serialization_excludes; [:confirmation_code]; end
|
2012-11-01 03:34:39 +08:00
|
|
|
|
|
|
|
def self.associated_shards(path)
|
|
|
|
[Shard.default]
|
|
|
|
end
|
2012-11-08 05:02:22 +08:00
|
|
|
|
2012-11-13 00:50:39 +08:00
|
|
|
def merge_candidates(break_on_first_found = false)
|
2012-11-08 05:02:22 +08:00
|
|
|
shards = self.class.associated_shards(self.path) if Enrollment.cross_shard_invitations?
|
2013-02-05 01:57:05 +08:00
|
|
|
shards ||= [self.shard]
|
2012-11-08 05:02:22 +08:00
|
|
|
scope = CommunicationChannel.active.by_path(self.path).of_type(self.path_type)
|
2012-11-13 00:50:39 +08:00
|
|
|
merge_candidates = {}
|
2012-11-08 05:02:22 +08:00
|
|
|
Shard.with_each_shard(shards) do
|
2012-11-13 00:50:39 +08:00
|
|
|
scope.where("user_id<>?", self.user_id).includes(: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?
|
2012-11-08 05:02:22 +08:00
|
|
|
end
|
2013-10-15 03:28:32 +08:00
|
|
|
|
|
|
|
def self.create_push(access_token, device_token)
|
|
|
|
(scope(:find, :shard) || Shard.current).activate do
|
|
|
|
connection.transaction do
|
|
|
|
cc = new
|
|
|
|
cc.path_type = CommunicationChannel::TYPE_PUSH
|
|
|
|
cc.path = device_token
|
|
|
|
cc.access_token = access_token
|
|
|
|
cc.workflow_state = 'active'
|
|
|
|
|
|
|
|
# save first, so we can put the global id in it
|
|
|
|
cc.save!
|
|
|
|
response = DeveloperKey.sns.client.create_platform_endpoint(
|
|
|
|
platform_application_arn: access_token.developer_key.sns_arn,
|
|
|
|
token: device_token,
|
|
|
|
custom_user_data: cc.global_id.to_s
|
|
|
|
)
|
|
|
|
|
|
|
|
cc.internal_path = response.data[:endpoint_arn]
|
|
|
|
cc.save!
|
|
|
|
cc
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|