2019-05-02 18:17:27 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2016-08-15 03:58:33 -04:00
|
|
|
class UserApiKey < ActiveRecord::Base
|
2020-09-29 05:57:48 -04:00
|
|
|
self.ignored_columns = [
|
|
|
|
"scopes" # TODO(2020-12-18): remove
|
|
|
|
]
|
2016-10-14 01:05:27 -04:00
|
|
|
|
|
|
|
SCOPES = {
|
|
|
|
read: [:get],
|
2019-02-12 23:49:25 -05:00
|
|
|
write: [:get, :post, :patch, :put, :delete],
|
2016-10-14 01:05:27 -04:00
|
|
|
message_bus: [[:post, 'message_bus']],
|
|
|
|
push: nil,
|
2019-04-01 13:18:53 -04:00
|
|
|
one_time_password: nil,
|
2016-10-14 01:05:27 -04:00
|
|
|
notifications: [[:post, 'message_bus'], [:get, 'notifications#index'], [:put, 'notifications#mark_read']],
|
2018-10-15 09:48:35 -04:00
|
|
|
session_info: [
|
|
|
|
[:get, 'session#current'],
|
|
|
|
[:get, 'users#topic_tracking_state'],
|
|
|
|
[:get, 'list#unread'],
|
2018-10-19 03:54:06 -04:00
|
|
|
[:get, 'list#new'],
|
|
|
|
[:get, 'list#latest']
|
2018-10-15 09:48:35 -04:00
|
|
|
]
|
2016-10-14 01:05:27 -04:00
|
|
|
}
|
|
|
|
|
2016-08-15 03:58:33 -04:00
|
|
|
belongs_to :user
|
2020-09-29 05:57:48 -04:00
|
|
|
has_many :scopes, class_name: "UserApiKeyScope", dependent: :destroy
|
2016-08-15 03:58:33 -04:00
|
|
|
|
2020-04-07 09:42:52 -04:00
|
|
|
scope :active, -> { where(revoked_at: nil) }
|
|
|
|
scope :with_key, ->(key) { where(key_hash: ApiKey.hash_key(key)) }
|
|
|
|
|
|
|
|
after_initialize :generate_key
|
|
|
|
|
|
|
|
def generate_key
|
|
|
|
if !self.key_hash
|
|
|
|
@key ||= SecureRandom.hex
|
|
|
|
self.key_hash = ApiKey.hash_key(@key)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def key
|
|
|
|
raise ApiKey::KeyAccessError.new "API key is only accessible immediately after creation" unless key_available?
|
|
|
|
@key
|
|
|
|
end
|
|
|
|
|
|
|
|
def key_available?
|
|
|
|
@key.present?
|
|
|
|
end
|
|
|
|
|
2020-09-29 05:57:48 -04:00
|
|
|
# Scopes allowed to be requested by external services
|
2016-10-14 01:05:27 -04:00
|
|
|
def self.allowed_scopes
|
|
|
|
Set.new(SiteSetting.allow_user_api_key_scopes.split("|"))
|
2016-08-15 03:58:33 -04:00
|
|
|
end
|
2016-10-14 01:05:27 -04:00
|
|
|
|
|
|
|
def self.available_scopes
|
|
|
|
@available_scopes ||= Set.new(SCOPES.keys.map(&:to_s))
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.allow_permission?(permission, env)
|
|
|
|
verb, action = permission
|
|
|
|
actual_verb = env["REQUEST_METHOD"] || ""
|
|
|
|
|
|
|
|
return false unless actual_verb.downcase == verb.to_s
|
|
|
|
return true unless action
|
|
|
|
|
|
|
|
# not a rails route, special handling
|
|
|
|
return true if action == "message_bus" && env["PATH_INFO"] =~ /^\/message-bus\/.*\/poll/
|
|
|
|
|
|
|
|
params = env['action_dispatch.request.path_parameters']
|
|
|
|
|
|
|
|
return false unless params
|
|
|
|
|
|
|
|
actual_action = "#{params[:controller]}##{params[:action]}"
|
|
|
|
actual_action == action
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.allow_scope?(name, env)
|
|
|
|
if allowed = SCOPES[name.to_sym]
|
|
|
|
good = allowed.any? do |permission|
|
|
|
|
allow_permission?(permission, env)
|
|
|
|
end
|
|
|
|
|
|
|
|
good || allow_permission?([:post, 'user_api_keys#revoke'], env)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def has_push?
|
2020-09-29 05:57:48 -04:00
|
|
|
scopes.any? { |s| s.name == "push" || s.name == "notifications" } &&
|
|
|
|
push_url.present? &&
|
|
|
|
SiteSetting.allowed_user_api_push_urls.include?(push_url)
|
2016-10-14 01:05:27 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def allow?(env)
|
2020-09-29 05:57:48 -04:00
|
|
|
scopes.any? do |s|
|
|
|
|
UserApiKey.allow_scope?(s.name, env)
|
|
|
|
end || is_revoke_self_request?(env)
|
2016-10-14 01:05:27 -04:00
|
|
|
end
|
|
|
|
|
2019-04-01 13:18:53 -04:00
|
|
|
def self.invalid_auth_redirect?(auth_redirect)
|
2019-11-14 15:10:51 -05:00
|
|
|
SiteSetting.allowed_user_api_auth_redirects
|
|
|
|
.split('|')
|
|
|
|
.none? { |u| WildcardUrlChecker.check_url(u, auth_redirect) }
|
2019-04-01 13:18:53 -04:00
|
|
|
end
|
2020-09-29 05:57:48 -04:00
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def is_revoke_self_request?(env)
|
|
|
|
UserApiKey.allow_permission?([:post, 'user_api_keys#revoke'], env) && (env[:id].nil? || env[:id].to_i == id)
|
|
|
|
end
|
2016-08-15 03:58:33 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
# == Schema Information
|
|
|
|
#
|
|
|
|
# Table name: user_api_keys
|
|
|
|
#
|
|
|
|
# id :integer not null, primary key
|
|
|
|
# user_id :integer not null
|
|
|
|
# client_id :string not null
|
|
|
|
# application_name :string not null
|
|
|
|
# push_url :string
|
2019-01-11 14:29:56 -05:00
|
|
|
# created_at :datetime not null
|
|
|
|
# updated_at :datetime not null
|
2016-10-31 05:32:11 -04:00
|
|
|
# revoked_at :datetime
|
|
|
|
# scopes :text default([]), not null, is an Array
|
2018-08-20 11:36:14 -04:00
|
|
|
# last_used_at :datetime not null
|
2020-04-07 09:42:52 -04:00
|
|
|
# key_hash :string not null
|
2016-08-15 03:58:33 -04:00
|
|
|
#
|
|
|
|
# Indexes
|
|
|
|
#
|
2016-10-31 05:32:11 -04:00
|
|
|
# index_user_api_keys_on_client_id (client_id) UNIQUE
|
2020-04-08 12:49:18 -04:00
|
|
|
# index_user_api_keys_on_key_hash (key_hash) UNIQUE
|
2016-08-15 03:58:33 -04:00
|
|
|
# index_user_api_keys_on_user_id (user_id)
|
|
|
|
#
|