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/>.
|
|
|
|
#
|
|
|
|
|
|
|
|
module AuthenticationMethods
|
2012-08-15 04:24:39 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def authorized(*groups)
|
|
|
|
authorized_roles = groups
|
|
|
|
return true
|
|
|
|
end
|
2012-08-15 04:24:39 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def authorized_roles
|
|
|
|
@authorized_roles ||= []
|
|
|
|
end
|
2012-08-15 04:24:39 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def consume_authorized_roles
|
|
|
|
authorized_roles = []
|
|
|
|
end
|
2012-08-15 04:24:39 +08:00
|
|
|
|
2011-03-08 05:02:56 +08:00
|
|
|
def load_pseudonym_from_policy
|
|
|
|
skip_session_save = false
|
2011-03-11 06:26:24 +08:00
|
|
|
if session.to_hash.empty? && # if there's already some session data, defer to normal auth
|
|
|
|
(policy_encoded = params['Policy']) &&
|
2011-03-08 05:02:56 +08:00
|
|
|
(signature = params['Signature']) &&
|
|
|
|
signature == Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new('sha1'), Attachment.shared_secret, policy_encoded)).gsub(/\n/, '') &&
|
|
|
|
(policy = JSON.parse(Base64.decode64(policy_encoded)) rescue nil) &&
|
|
|
|
policy['conditions'] &&
|
|
|
|
(credential = policy['conditions'].detect{ |cond| cond.is_a?(Hash) && cond.has_key?("pseudonym_id") })
|
|
|
|
skip_session_save = true
|
|
|
|
@policy_pseudonym_id = credential['pseudonym_id']
|
|
|
|
# so that we don't have to explicitly skip verify_authenticity_token
|
|
|
|
params[self.class.request_forgery_protection_token] ||= form_authenticity_token
|
|
|
|
end
|
|
|
|
yield if block_given?
|
|
|
|
session.destroy if skip_session_save
|
|
|
|
end
|
|
|
|
|
2011-12-23 04:26:05 +08:00
|
|
|
class AccessTokenError < Exception
|
|
|
|
end
|
|
|
|
|
|
|
|
def load_pseudonym_from_access_token
|
2012-08-02 04:37:40 +08:00
|
|
|
return unless api_request? || params[:action] == 'oauth2_logout'
|
2011-12-23 04:26:05 +08:00
|
|
|
|
|
|
|
auth_header = ActionController::HttpAuthentication::Basic.authorization(request)
|
|
|
|
token_string = if auth_header.present? && (header_parts = auth_header.split(' ', 2)) && header_parts[0] == 'Bearer'
|
|
|
|
header_parts[1]
|
|
|
|
elsif params[:access_token].present?
|
|
|
|
params[:access_token]
|
|
|
|
end
|
|
|
|
|
|
|
|
if token_string
|
2012-10-18 04:04:22 +08:00
|
|
|
@access_token = AccessToken.authenticate(token_string)
|
|
|
|
if !@access_token
|
2011-12-23 04:26:05 +08:00
|
|
|
raise AccessTokenError
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
2011-07-28 06:43:25 +08:00
|
|
|
@current_user = @access_token.user
|
2012-01-14 06:58:17 +08:00
|
|
|
@current_pseudonym = @current_user.find_pseudonym_for_account(@domain_root_account, true)
|
2011-12-22 02:59:05 +08:00
|
|
|
unless @current_user && @current_pseudonym
|
2011-12-23 04:26:05 +08:00
|
|
|
raise AccessTokenError
|
2011-07-28 06:43:25 +08:00
|
|
|
end
|
|
|
|
@access_token.used!
|
2011-07-20 01:47:59 +08:00
|
|
|
end
|
2011-12-23 04:26:05 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def load_user
|
|
|
|
@current_user = @current_pseudonym = nil
|
|
|
|
|
2011-12-23 06:11:53 +08:00
|
|
|
load_pseudonym_from_access_token
|
2011-07-20 01:47:59 +08:00
|
|
|
|
2011-12-23 04:26:05 +08:00
|
|
|
if !@current_pseudonym
|
2011-10-04 23:21:15 +08:00
|
|
|
if @policy_pseudonym_id
|
2011-05-27 07:41:43 +08:00
|
|
|
@current_pseudonym = Pseudonym.find_by_id(@policy_pseudonym_id)
|
|
|
|
else
|
2011-11-12 01:44:17 +08:00
|
|
|
@pseudonym_session = PseudonymSession.find
|
2011-05-27 07:41:43 +08:00
|
|
|
@current_pseudonym = @pseudonym_session && @pseudonym_session.record
|
|
|
|
end
|
|
|
|
if params[:login_success] == '1' && !@current_pseudonym
|
|
|
|
# they just logged in successfully, but we can't find the pseudonym now?
|
|
|
|
# sounds like somebody hates cookies.
|
|
|
|
return redirect_to(login_url(:needs_cookies => '1'))
|
|
|
|
end
|
|
|
|
@current_user = @current_pseudonym && @current_pseudonym.user
|
2011-07-28 06:43:25 +08:00
|
|
|
|
|
|
|
if api_request?
|
2011-08-10 06:54:59 +08:00
|
|
|
# only allow api_key to be used if basic auth was sent, not if they're
|
|
|
|
# just using an app session
|
2011-12-23 04:26:05 +08:00
|
|
|
# this basic auth support is deprecated and marked for removal in 2012
|
2012-03-20 06:24:27 +08:00
|
|
|
if @pseudonym_session.try(:used_basic_auth?) && params[:api_key].present?
|
|
|
|
Shard.default.activate { @developer_key = DeveloperKey.find_by_api_key(params[:api_key]) }
|
|
|
|
end
|
2013-03-12 04:17:20 +08:00
|
|
|
@developer_key || request.get? || !allow_forgery_protection || form_authenticity_token == form_authenticity_param || form_authenticity_token == request.headers['X-CSRF-Token'] || raise(AccessTokenError)
|
2011-07-28 06:43:25 +08:00
|
|
|
end
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
2011-07-20 01:47:59 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
if @current_user && @current_user.unavailable?
|
|
|
|
@current_pseudonym = nil
|
2011-12-23 04:26:05 +08:00
|
|
|
@current_user = nil
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
|
|
|
|
2011-07-09 02:59:34 +08:00
|
|
|
if @current_user && %w(become_user_id me become_teacher become_student).any? { |k| params.key?(k) }
|
|
|
|
request_become_user = nil
|
2011-02-01 09:57:29 +08:00
|
|
|
if params[:become_user_id]
|
2011-07-09 02:59:34 +08:00
|
|
|
request_become_user = User.find_by_id(params[:become_user_id])
|
2011-02-01 09:57:29 +08:00
|
|
|
elsif params.keys.include?('me')
|
2011-07-09 02:59:34 +08:00
|
|
|
request_become_user = @current_user
|
2011-02-01 09:57:29 +08:00
|
|
|
elsif params.keys.include?('become_teacher')
|
|
|
|
course = Course.find(params[:course_id] || params[:id]) rescue nil
|
|
|
|
teacher = course.teachers.first if course
|
|
|
|
if teacher
|
2011-07-09 02:59:34 +08:00
|
|
|
request_become_user = teacher
|
2011-02-01 09:57:29 +08:00
|
|
|
else
|
2011-05-07 02:44:34 +08:00
|
|
|
flash[:error] = I18n.t('lib.auth.errors.teacher_not_found', "No teacher found")
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
|
|
|
elsif params.keys.include?('become_student')
|
|
|
|
course = Course.find(params[:course_id] || params[:id]) rescue nil
|
|
|
|
student = course.students.first if course
|
|
|
|
if student
|
2011-07-09 02:59:34 +08:00
|
|
|
request_become_user = student
|
2011-02-01 09:57:29 +08:00
|
|
|
else
|
2011-05-07 02:44:34 +08:00
|
|
|
flash[:error] = I18n.t('lib.auth.errors.student_not_found', "No student found")
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
|
|
|
end
|
2011-07-09 02:59:34 +08:00
|
|
|
|
2012-02-21 06:57:32 +08:00
|
|
|
if request_become_user && request_become_user.id != session[:become_user_id].to_i && request_become_user.can_masquerade?(@current_user, @domain_root_account)
|
2011-05-28 00:15:19 +08:00
|
|
|
params_without_become = params.dup
|
|
|
|
params_without_become.delete_if {|k,v| [ 'become_user_id', 'become_teacher', 'become_student', 'me' ].include? k }
|
|
|
|
params_without_become[:only_path] = true
|
|
|
|
session[:masquerade_return_to] = url_for(params_without_become)
|
2011-07-09 02:59:34 +08:00
|
|
|
return redirect_to user_masquerade_url(request_become_user.id)
|
2011-05-28 00:15:19 +08:00
|
|
|
end
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
|
|
|
|
2012-01-24 02:56:25 +08:00
|
|
|
as_user_id = api_request? && params[:as_user_id].presence
|
2011-10-07 05:24:25 +08:00
|
|
|
as_user_id ||= session[:become_user_id]
|
2011-12-28 00:52:51 +08:00
|
|
|
if as_user_id
|
|
|
|
begin
|
|
|
|
user = api_find(User, as_user_id)
|
|
|
|
rescue ActiveRecord::RecordNotFound
|
|
|
|
end
|
2012-02-21 06:57:32 +08:00
|
|
|
if user && user.can_masquerade?(@current_user, @domain_root_account)
|
2011-10-21 03:28:32 +08:00
|
|
|
@real_current_user = @current_user
|
|
|
|
@current_user = user
|
2012-02-07 08:03:33 +08:00
|
|
|
@real_current_pseudonym = @current_pseudonym
|
|
|
|
@current_pseudonym = @current_user.find_pseudonym_for_account(@domain_root_account, true)
|
2011-10-21 03:28:32 +08:00
|
|
|
logger.warn "#{@real_current_user.name}(#{@real_current_user.id}) impersonating #{@current_user.name} on page #{request.url}"
|
2011-12-28 00:52:51 +08:00
|
|
|
elsif api_request?
|
|
|
|
# fail silently for UI, but not for API
|
|
|
|
render :json => {:errors => "Invalid as_user_id"}, :status => :unauthorized
|
|
|
|
return false
|
2011-10-21 03:28:32 +08:00
|
|
|
end
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
@current_user
|
|
|
|
end
|
|
|
|
private :load_user
|
2011-12-23 04:26:05 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def require_user
|
2013-02-13 04:58:04 +08:00
|
|
|
if @current_user && @current_pseudonym
|
|
|
|
true
|
|
|
|
else
|
2012-03-08 08:32:27 +08:00
|
|
|
redirect_to_login
|
2013-02-13 04:58:04 +08:00
|
|
|
false
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
protected :require_user
|
|
|
|
|
2013-04-12 07:05:16 +08:00
|
|
|
def clean_return_to(url)
|
|
|
|
return nil if url.blank?
|
|
|
|
uri = URI.parse(url)
|
|
|
|
return nil unless uri.path[0] == ?/
|
|
|
|
return "#{request.protocol}#{request.host_with_port}#{uri.path}#{uri.query && "?#{uri.query}"}#{uri.fragment && "##{uri.fragment}"}"
|
|
|
|
end
|
|
|
|
|
|
|
|
def return_to(url, fallback)
|
|
|
|
url = clean_return_to(url) || clean_return_to(fallback)
|
|
|
|
redirect_to url
|
|
|
|
end
|
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def store_location(uri=nil, overwrite=true)
|
|
|
|
if overwrite || !session[:return_to]
|
2013-04-12 07:05:16 +08:00
|
|
|
uri ||= request.get? ? request.request_uri : request.referrer
|
|
|
|
session[:return_to] = clean_return_to(uri)
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
protected :store_location
|
|
|
|
|
|
|
|
def redirect_back_or_default(default)
|
|
|
|
redirect_to(session[:return_to] || default)
|
2012-05-09 10:27:16 +08:00
|
|
|
session.delete(:return_to)
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
|
|
|
protected :redirect_back_or_default
|
|
|
|
|
2011-06-30 04:05:59 +08:00
|
|
|
def redirect_to_referrer_or_default(default)
|
|
|
|
redirect_to(:back)
|
|
|
|
rescue ActionController::RedirectBackError
|
|
|
|
redirect_to(default)
|
|
|
|
end
|
|
|
|
|
2012-03-08 08:32:27 +08:00
|
|
|
def redirect_to_login
|
|
|
|
respond_to do |format|
|
2012-08-15 04:24:39 +08:00
|
|
|
format.html {
|
|
|
|
store_location
|
|
|
|
flash[:warning] = I18n.t('lib.auth.errors.not_authenticated', "You must be logged in to access this page") unless request.path == '/'
|
|
|
|
opts = {}
|
|
|
|
opts[:canvas_login] = 1 if params[:canvas_login]
|
|
|
|
redirect_to login_url(opts) # should this have :no_auto => 'true' ?
|
|
|
|
}
|
2013-03-27 00:57:36 +08:00
|
|
|
format.json { render_json_unauthorized }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def render_json_unauthorized
|
|
|
|
add_www_authenticate_header if api_request? && !@current_user
|
|
|
|
if @current_user
|
|
|
|
render :json => {
|
|
|
|
:status => I18n.t('lib.auth.status_unauthorized', 'unauthorized'),
|
|
|
|
:errors => { :message => I18n.t('lib.auth.not_authorized', "user not authorized to perform that action") }
|
|
|
|
},
|
|
|
|
:status => :unauthorized
|
|
|
|
else
|
|
|
|
render :json => {
|
|
|
|
:status => I18n.t('lib.auth.status_unauthenticated', 'unauthenticated'),
|
|
|
|
:errors => { :message => I18n.t('lib.auth.authentication_required', "user authorization required") }
|
|
|
|
},
|
|
|
|
:status => :unauthorized
|
2012-03-08 08:32:27 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-03-27 00:57:36 +08:00
|
|
|
def add_www_authenticate_header
|
|
|
|
response['WWW-Authenticate'] = %{Bearer realm="canvas-lms"}
|
|
|
|
end
|
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
# Reset the session, and copy the specified keys over to the new session.
|
|
|
|
# Please consider the security implications of any keys you copy over.
|
|
|
|
def reset_session_saving_keys(*keys)
|
|
|
|
# can't use slice, because session has a different ctor than a normal hash
|
|
|
|
saved = {}
|
2011-11-03 04:59:41 +08:00
|
|
|
keys.each { |k| saved[k] = session[k] if session[k] }
|
2011-02-01 09:57:29 +08:00
|
|
|
reset_session
|
2011-11-03 04:59:41 +08:00
|
|
|
saved.each_pair { |k, v| session[k] = v }
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
|
|
|
|
2011-07-16 00:30:31 +08:00
|
|
|
def reset_session_for_login
|
2012-08-01 05:41:13 +08:00
|
|
|
reset_session_saving_keys(:return_to, :oauth2, :confirm, :enrollment, :expected_user_id, :masquerade_return_to)
|
2011-07-16 00:30:31 +08:00
|
|
|
end
|
|
|
|
|
2012-04-04 04:17:56 +08:00
|
|
|
def initiate_delegated_login(current_host=nil)
|
2011-06-03 01:26:20 +08:00
|
|
|
is_delegated = @domain_root_account.delegated_authentication? && !params[:canvas_login]
|
2012-12-27 03:55:44 +08:00
|
|
|
is_cas = is_delegated && @domain_root_account.cas_authentication?
|
|
|
|
is_saml = is_delegated && @domain_root_account.saml_authentication?
|
2011-06-03 01:26:20 +08:00
|
|
|
if is_cas
|
|
|
|
initiate_cas_login
|
|
|
|
return true
|
|
|
|
elsif is_saml
|
2012-12-27 03:55:44 +08:00
|
|
|
|
|
|
|
if @domain_root_account.auth_discovery_url
|
|
|
|
redirect_to @domain_root_account.auth_discovery_url
|
|
|
|
else
|
|
|
|
initiate_saml_login(current_host)
|
|
|
|
end
|
|
|
|
|
2011-06-03 01:26:20 +08:00
|
|
|
return true
|
|
|
|
end
|
|
|
|
false
|
|
|
|
end
|
|
|
|
|
|
|
|
def initiate_cas_login(cas_client = nil)
|
2011-07-16 00:30:31 +08:00
|
|
|
reset_session_for_login
|
2012-06-23 02:19:40 +08:00
|
|
|
config = { :cas_base_url => @domain_root_account.account_authorization_config.auth_base }
|
|
|
|
cas_client ||= CASClient::Client.new(config)
|
2012-08-17 00:13:30 +08:00
|
|
|
delegated_auth_redirect(cas_client.add_service_to_login_url(cas_login_url))
|
2011-06-03 01:26:20 +08:00
|
|
|
end
|
|
|
|
|
2012-09-29 06:02:02 +08:00
|
|
|
def initiate_saml_login(current_host=nil, aac=nil)
|
2011-07-16 00:30:31 +08:00
|
|
|
reset_session_for_login
|
2012-09-29 06:02:02 +08:00
|
|
|
aac ||= @domain_root_account.account_authorization_config
|
2012-04-04 04:17:56 +08:00
|
|
|
settings = aac.saml_settings(current_host)
|
2012-01-28 09:33:18 +08:00
|
|
|
request = Onelogin::Saml::AuthRequest.new(settings)
|
|
|
|
forward_url = request.generate_request
|
|
|
|
if aac.debugging? && !aac.debug_get(:request_id)
|
|
|
|
aac.debug_set(:request_id, request.id)
|
|
|
|
aac.debug_set(:to_idp_url, forward_url)
|
|
|
|
aac.debug_set(:to_idp_xml, request.request_xml)
|
|
|
|
aac.debug_set(:debugging, "Forwarding user to IdP for authentication")
|
|
|
|
end
|
|
|
|
delegated_auth_redirect(forward_url)
|
2011-10-28 02:51:15 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def delegated_auth_redirect(uri)
|
2011-12-29 07:07:29 +08:00
|
|
|
redirect_to(delegated_auth_redirect_uri(uri))
|
|
|
|
end
|
|
|
|
|
|
|
|
def delegated_auth_redirect_uri(uri)
|
|
|
|
uri
|
2011-06-03 01:26:20 +08:00
|
|
|
end
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|