canvas-lms/app/models/access_token.rb

115 lines
3.0 KiB
Ruby

class AccessToken < ActiveRecord::Base
attr_reader :full_token
belongs_to :developer_key
belongs_to :user
attr_accessible :user, :purpose, :expires_at, :developer_key, :regenerate, :scopes, :remember_access
serialize :scopes, Array
validate :must_only_include_valid_scopes
# For user-generated tokens, purpose can be manually set.
# For app-generated tokens, this should be generated based
# on the scope defined in the auth process (scope has not
# yet been implemented)
scope :active, lambda { where("expires_at IS NULL OR expires_at>?", Time.zone.now) }
TOKEN_SIZE = 64
OAUTH2_SCOPE_NAMESPACE = '/auth/'
ALLOWED_SCOPES = ["#{OAUTH2_SCOPE_NAMESPACE}userinfo"]
before_create :generate_token
def self.authenticate(token_string)
token = self.where(:crypted_token => hashed_token(token_string)).first
token = nil unless token.try(:usable?)
token
end
def self.hashed_token(token)
# This use of hmac is a bit odd, since we aren't really signing a message
# other than the random token string itself.
# However, what we're essentially looking for is a hash of the token
# "signed" or concatenated with the secret encryption key, so this is perfect.
Canvas::Security.hmac_sha1(token)
end
def usable?
user_id && !expired?
end
def app_name
developer_key.try(:name) || "No App"
end
def record_last_used_threshold
Setting.get('access_token_last_used_threshold', 10.minutes).to_i
end
def used!
if !last_used_at || last_used_at < record_last_used_threshold.ago
self.last_used_at = Time.now
self.save
end
end
def expired?
expires_at && expires_at < Time.now
end
def token=(new_token)
self.crypted_token = AccessToken.hashed_token(new_token)
@full_token = new_token
self.token_hint = new_token[0,5]
end
def clear_full_token!
@full_token = nil
end
def generate_token(overwrite=false)
if overwrite || !self.crypted_token
self.token = AutoHandle.generate(nil, TOKEN_SIZE)
end
end
def protected_token?
developer_key != DeveloperKey.default
end
def regenerate=(val)
if val == '1' && !protected_token?
generate_token(true)
end
end
def visible_token
if protected_token?
nil
elsif full_token
full_token
else
"#{token_hint}..."
end
end
#Scoped token convenience method
def scoped_to?(req_scopes)
return req_scopes.size == 0 if scopes.nil?
scopes.size == req_scopes.size &&
scopes.all? do |scope|
req_scopes.any? {|req_scope| scope[/(^|\/)#{req_scope}$/]}
end
end
def must_only_include_valid_scopes
return true if scopes.nil?
errors.add(:scopes, "must match accepted scopes") unless scopes.all? {|scope| ALLOWED_SCOPES.include?(scope)}
end
# It's encrypted, but end users still shouldn't see this.
# The hint is only returned in visible_token, if protected_token is false.
def self.serialization_excludes; [:crypted_token, :token_hint]; end
end