canvas-lms/app/models/session_persistence_token.rb

90 lines
3.3 KiB
Ruby

#
# 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/>.
#
require 'authlogic/crypto_providers/bcrypt'
# A SessionPersistenceToken is a one-time-use "remember me" token to maintain a
# user's login across browser sessions. It has an expiry, and it's destroyed
# when the user logs out, but most importantly, it is destroyed after the first
# time it is used to authenticate the user.
#
# The token is comprised of three fields:
# <token_id:pseudonym.persistence_token:random_uuid>
#
# The first field allows efficient lookup, the second field verifies that the
# pseudonym hasn't changed their password or anything else that changes the
# persistence_token, and the last field is an unguessable token identifier
# generated at random. This last field is password-equivalent, so it's stored
# as a salt+hash server-side.
#
# much of the theory here is based on this blog post:
# http://fishbowl.pastiche.org/2004/01/19/persistent_login_cookie_best_practice/
#
# See the PseudonymSession model for the *_cookie methods that use this class.
class SessionPersistenceToken < ActiveRecord::Base
belongs_to :pseudonym
attr_accessible :pseudonym, :crypted_token, :token_salt, :uncrypted_token
attr_accessor :uncrypted_token
validates_presence_of :pseudonym_id, :crypted_token, :token_salt
def self.generate(pseudonym)
salt = ActiveSupport::SecureRandom.hex(8)
token = ActiveSupport::SecureRandom.hex(32)
self.create!(:pseudonym => pseudonym,
:token_salt => salt,
:uncrypted_token => token,
:crypted_token => self.hashed_token(salt, token))
end
def self.hashed_token(salt, token)
self.crypto.encrypt(salt, token)
end
def self.crypto
Authlogic::CryptoProviders::BCrypt
end
def self.find_by_pseudonym_credentials(creds)
token_id, persistence_token, uuid = creds.split("::")
return unless token_id.present? && persistence_token.present? && uuid.present?
token = self.find_by_id(token_id)
return unless token
return unless token.valid_token?(persistence_token, uuid)
return token
end
def valid_token?(persistence_token, uncrypted_token)
# if the pseudonym is marked deleted, the token can still be marked as
# valid, but the actual login step will fail as expected.
self.pseudonym &&
self.pseudonym.persistence_token == persistence_token &&
self.class.crypto.matches?(self.crypted_token, self.token_salt, uncrypted_token)
end
def pseudonym_credentials
raise "can't build pseudonym_credentials except on just-generated token" unless uncrypted_token
"#{id}::#{pseudonym.persistence_token}::#{uncrypted_token}"
end
def use!
destroy
return pseudonym
end
end