2017-12-11 11:07:22 +11:00
|
|
|
# frozen_string_literal: true
|
2020-10-06 17:20:15 +01:00
|
|
|
require_relative "../route_matcher"
|
2017-12-11 11:07:22 +11:00
|
|
|
|
2021-11-17 23:27:30 +03:00
|
|
|
# You may have seen references to v0 and v1 of our auth cookie in the codebase
|
|
|
|
|
# and you're not sure how they differ, so here is an explanation:
|
|
|
|
|
#
|
|
|
|
|
# From the very early days of Discourse, the auth cookie (_t) consisted only of
|
|
|
|
|
# a 32 characters random string that Discourse used to identify/lookup the
|
|
|
|
|
# current user. We didn't include any metadata with the cookie or encrypt/sign
|
|
|
|
|
# it.
|
|
|
|
|
#
|
|
|
|
|
# That was v0 of the auth cookie until Nov 2021 when we merged a change that
|
|
|
|
|
# required us to store additional metadata with the cookie so we could get more
|
|
|
|
|
# information about current user early in the request lifecycle before we
|
|
|
|
|
# performed database lookup. We also started encrypting and signing the cookie
|
|
|
|
|
# to prevent tampering and obfuscate user information that we include in the
|
|
|
|
|
# cookie. This is v1 of our auth cookie and we still use it to this date.
|
|
|
|
|
#
|
|
|
|
|
# We still accept v0 of the auth cookie to keep users logged in, but upon
|
|
|
|
|
# cookie rotation (which happen every 10 minutes) they'll be switched over to
|
|
|
|
|
# the v1 format.
|
|
|
|
|
#
|
|
|
|
|
# We'll drop support for v0 after Discourse 2.9 is released.
|
|
|
|
|
|
2013-10-09 15:10:37 +11:00
|
|
|
class Auth::DefaultCurrentUserProvider
|
2018-02-19 10:12:51 +11:00
|
|
|
CURRENT_USER_KEY ||= "_DISCOURSE_CURRENT_USER"
|
2022-07-04 17:01:19 +02:00
|
|
|
USER_TOKEN_KEY ||= "_DISCOURSE_USER_TOKEN"
|
2018-02-19 10:12:51 +11:00
|
|
|
API_KEY ||= "api_key"
|
2019-03-08 09:13:31 -07:00
|
|
|
API_USERNAME ||= "api_username"
|
|
|
|
|
HEADER_API_KEY ||= "HTTP_API_KEY"
|
|
|
|
|
HEADER_API_USERNAME ||= "HTTP_API_USERNAME"
|
|
|
|
|
HEADER_API_USER_EXTERNAL_ID ||= "HTTP_API_USER_EXTERNAL_ID"
|
|
|
|
|
HEADER_API_USER_ID ||= "HTTP_API_USER_ID"
|
2020-05-12 13:35:36 +01:00
|
|
|
PARAMETER_USER_API_KEY ||= "user_api_key"
|
2018-02-19 10:12:51 +11:00
|
|
|
USER_API_KEY ||= "HTTP_USER_API_KEY"
|
|
|
|
|
USER_API_CLIENT_ID ||= "HTTP_USER_API_CLIENT_ID"
|
|
|
|
|
API_KEY_ENV ||= "_DISCOURSE_API"
|
|
|
|
|
USER_API_KEY_ENV ||= "_DISCOURSE_USER_API"
|
2018-03-13 16:48:40 -04:00
|
|
|
TOKEN_COOKIE ||= ENV["DISCOURSE_TOKEN_COOKIE"] || "_t"
|
2018-02-19 10:12:51 +11:00
|
|
|
PATH_INFO ||= "PATH_INFO"
|
2016-07-28 12:58:49 +10:00
|
|
|
COOKIE_ATTEMPTS_PER_MIN ||= 10
|
2018-03-06 16:49:31 +11:00
|
|
|
BAD_TOKEN ||= "_DISCOURSE_BAD_TOKEN"
|
2021-11-17 23:27:30 +03:00
|
|
|
DECRYPTED_AUTH_COOKIE = "_DISCOURSE_DECRYPTED_AUTH_COOKIE"
|
|
|
|
|
|
|
|
|
|
TOKEN_SIZE = 32
|
2013-10-09 15:10:37 +11:00
|
|
|
|
2020-05-13 12:54:28 +01:00
|
|
|
PARAMETER_API_PATTERNS ||= [
|
2020-10-06 17:20:15 +01:00
|
|
|
RouteMatcher.new(
|
|
|
|
|
methods: :get,
|
|
|
|
|
actions: [
|
2020-05-12 13:35:36 +01:00
|
|
|
"posts#latest",
|
|
|
|
|
"posts#user_posts_feed",
|
|
|
|
|
"groups#posts_feed",
|
|
|
|
|
"groups#mentions_feed",
|
2020-05-12 16:08:35 +01:00
|
|
|
"list#user_topics_feed",
|
2020-05-12 13:35:36 +01:00
|
|
|
"list#category_feed",
|
2020-05-12 16:08:35 +01:00
|
|
|
"topics#feed",
|
|
|
|
|
"badges#show",
|
|
|
|
|
"tags#tag_feed",
|
|
|
|
|
"tags#show",
|
|
|
|
|
*%i[latest unread new read posted bookmarks].map { |f| "list##{f}_feed" },
|
|
|
|
|
*%i[all yearly quarterly monthly weekly daily].map { |p| "list#top_#{p}_feed" },
|
|
|
|
|
*%i[latest unread new read posted bookmarks].map { |f| "tags#show_#{f}" },
|
2020-05-12 13:35:36 +01:00
|
|
|
],
|
2020-10-06 17:20:15 +01:00
|
|
|
formats: :rss,
|
2021-05-06 12:59:52 +10:00
|
|
|
),
|
2020-10-06 17:20:15 +01:00
|
|
|
RouteMatcher.new(methods: :get, actions: "users#bookmarks", formats: :ics),
|
|
|
|
|
RouteMatcher.new(methods: :post, actions: "admin/email#handle_mail", formats: nil),
|
2020-05-12 13:35:36 +01:00
|
|
|
]
|
|
|
|
|
|
2021-11-17 23:27:30 +03:00
|
|
|
def self.find_v0_auth_cookie(request)
|
2022-06-07 13:00:25 +02:00
|
|
|
cookie = request.cookies[TOKEN_COOKIE]
|
|
|
|
|
|
|
|
|
|
cookie if cookie&.valid_encoding? && cookie.present? && cookie.size == TOKEN_SIZE
|
2021-11-17 23:27:30 +03:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def self.find_v1_auth_cookie(env)
|
|
|
|
|
return env[DECRYPTED_AUTH_COOKIE] if env.key?(DECRYPTED_AUTH_COOKIE)
|
|
|
|
|
|
|
|
|
|
env[DECRYPTED_AUTH_COOKIE] = begin
|
|
|
|
|
request = ActionDispatch::Request.new(env)
|
2022-06-07 13:00:25 +02:00
|
|
|
cookie = request.cookies[TOKEN_COOKIE]
|
|
|
|
|
|
2021-11-17 23:27:30 +03:00
|
|
|
# don't even initialize a cookie jar if we don't have a cookie at all
|
2022-06-07 13:00:25 +02:00
|
|
|
if cookie&.valid_encoding? && cookie.present?
|
2022-04-20 19:15:40 +01:00
|
|
|
request.cookie_jar.encrypted[TOKEN_COOKIE]&.with_indifferent_access
|
2021-11-17 23:27:30 +03:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2013-10-09 15:10:37 +11:00
|
|
|
# do all current user initialization here
|
2018-09-04 14:17:05 +08:00
|
|
|
def initialize(env)
|
2013-10-09 15:10:37 +11:00
|
|
|
@env = env
|
|
|
|
|
@request = Rack::Request.new(env)
|
2022-07-04 17:01:19 +02:00
|
|
|
@user_token = env[USER_TOKEN_KEY]
|
2013-10-09 15:10:37 +11:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# our current user, return nil if none is found
|
|
|
|
|
def current_user
|
|
|
|
|
return @env[CURRENT_USER_KEY] if @env.key?(CURRENT_USER_KEY)
|
|
|
|
|
|
2014-10-24 13:38:00 +11:00
|
|
|
# bypass if we have the shared session header
|
|
|
|
|
if shared_key = @env["HTTP_X_SHARED_SESSION_KEY"]
|
2019-12-03 10:05:53 +01:00
|
|
|
uid = Discourse.redis.get("shared_session_key_#{shared_key}")
|
2014-10-24 13:38:00 +11:00
|
|
|
user = nil
|
|
|
|
|
user = User.find_by(id: uid.to_i) if uid
|
|
|
|
|
@env[CURRENT_USER_KEY] = user
|
|
|
|
|
return user
|
|
|
|
|
end
|
|
|
|
|
|
2014-05-23 08:13:25 +10:00
|
|
|
request = @request
|
2013-10-09 15:10:37 +11:00
|
|
|
|
2017-02-17 11:02:33 -05:00
|
|
|
user_api_key = @env[USER_API_KEY]
|
2020-05-12 13:35:36 +01:00
|
|
|
api_key = @env[HEADER_API_KEY]
|
|
|
|
|
|
|
|
|
|
if !@env.blank? && request[PARAMETER_USER_API_KEY] && api_parameter_allowed?
|
|
|
|
|
user_api_key ||= request[PARAMETER_USER_API_KEY]
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
api_key ||= request[API_KEY] if !@env.blank? && request[API_KEY] && api_parameter_allowed?
|
2017-02-17 11:02:33 -05:00
|
|
|
|
2021-11-17 23:27:30 +03:00
|
|
|
auth_token = find_auth_token
|
2013-10-09 15:10:37 +11:00
|
|
|
current_user = nil
|
|
|
|
|
|
2021-11-17 23:27:30 +03:00
|
|
|
if auth_token
|
2016-08-09 10:02:18 +10:00
|
|
|
limiter = RateLimiter.new(nil, "cookie_auth_#{request.ip}", COOKIE_ATTEMPTS_PER_MIN, 60)
|
|
|
|
|
|
2018-02-09 19:09:54 -05:00
|
|
|
if limiter.can_perform?
|
2022-07-04 17:01:19 +02:00
|
|
|
@env[USER_TOKEN_KEY] = @user_token =
|
|
|
|
|
begin
|
2020-06-03 16:36:51 +08:00
|
|
|
UserAuthToken.lookup(
|
|
|
|
|
auth_token,
|
|
|
|
|
seen: true,
|
|
|
|
|
user_agent: @env["HTTP_USER_AGENT"],
|
|
|
|
|
path: @env["REQUEST_PATH"],
|
|
|
|
|
client_ip: @request.ip,
|
|
|
|
|
)
|
|
|
|
|
rescue ActiveRecord::ReadOnlyError
|
|
|
|
|
nil
|
|
|
|
|
end
|
2017-02-13 14:01:01 -05:00
|
|
|
|
2017-01-31 17:21:37 -05:00
|
|
|
current_user = @user_token.try(:user)
|
2016-08-09 10:02:18 +10:00
|
|
|
end
|
2016-07-28 12:58:49 +10:00
|
|
|
|
2018-03-06 16:49:31 +11:00
|
|
|
if !current_user
|
|
|
|
|
@env[BAD_TOKEN] = true
|
2016-07-28 12:58:49 +10:00
|
|
|
begin
|
2018-09-04 14:17:05 +08:00
|
|
|
limiter.performed!
|
2016-07-28 12:58:49 +10:00
|
|
|
rescue RateLimiter::LimitExceeded
|
2018-02-09 19:09:54 -05:00
|
|
|
raise Discourse::InvalidAccess.new("Invalid Access", nil, delete_cookie: TOKEN_COOKIE)
|
2016-07-28 12:58:49 +10:00
|
|
|
end
|
|
|
|
|
end
|
2018-03-06 16:49:31 +11:00
|
|
|
elsif @env["HTTP_DISCOURSE_LOGGED_IN"]
|
|
|
|
|
@env[BAD_TOKEN] = true
|
2013-10-09 15:10:37 +11:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# possible we have an api call, impersonate
|
2017-02-17 11:02:33 -05:00
|
|
|
if api_key
|
2014-05-23 08:13:25 +10:00
|
|
|
current_user = lookup_api_user(api_key, request)
|
2021-11-17 23:27:30 +03:00
|
|
|
if !current_user
|
|
|
|
|
raise Discourse::InvalidAccess.new(
|
|
|
|
|
I18n.t("invalid_api_credentials"),
|
|
|
|
|
nil,
|
|
|
|
|
custom_message: "invalid_api_credentials",
|
|
|
|
|
)
|
|
|
|
|
end
|
2017-02-17 11:02:33 -05:00
|
|
|
raise Discourse::InvalidAccess if current_user.suspended? || !current_user.active
|
2021-11-17 23:27:30 +03:00
|
|
|
admin_api_key_limiter.performed! if !Rails.env.profile?
|
2014-05-23 08:13:25 +10:00
|
|
|
@env[API_KEY_ENV] = true
|
2013-10-09 15:10:37 +11:00
|
|
|
end
|
|
|
|
|
|
2016-08-15 17:58:33 +10:00
|
|
|
# user api key handling
|
2017-02-17 11:02:33 -05:00
|
|
|
if user_api_key
|
2021-11-17 23:27:30 +03:00
|
|
|
@hashed_user_api_key = ApiKey.hash_key(user_api_key)
|
2016-08-15 17:58:33 +10:00
|
|
|
|
2021-11-17 23:27:30 +03:00
|
|
|
user_api_key_obj =
|
|
|
|
|
UserApiKey
|
|
|
|
|
.active
|
|
|
|
|
.joins(:user)
|
|
|
|
|
.where(key_hash: @hashed_user_api_key)
|
|
|
|
|
.includes(:user, :scopes)
|
|
|
|
|
.first
|
2016-08-15 17:58:33 +10:00
|
|
|
|
2021-11-17 23:27:30 +03:00
|
|
|
raise Discourse::InvalidAccess unless user_api_key_obj
|
2016-08-15 17:58:33 +10:00
|
|
|
|
2021-11-17 23:27:30 +03:00
|
|
|
user_api_key_limiter_60_secs.performed!
|
|
|
|
|
user_api_key_limiter_1_day.performed!
|
2016-08-15 17:58:33 +10:00
|
|
|
|
2021-11-17 23:27:30 +03:00
|
|
|
user_api_key_obj.ensure_allowed!(@env)
|
|
|
|
|
|
|
|
|
|
current_user = user_api_key_obj.user
|
2017-02-17 11:02:33 -05:00
|
|
|
raise Discourse::InvalidAccess if current_user.suspended? || !current_user.active
|
2016-08-15 17:58:33 +10:00
|
|
|
|
2021-11-17 23:27:30 +03:00
|
|
|
user_api_key_obj.update_last_used(@env[USER_API_CLIENT_ID]) if can_write?
|
2016-08-15 17:58:33 +10:00
|
|
|
|
2016-12-16 12:05:20 +11:00
|
|
|
@env[USER_API_KEY_ENV] = true
|
2016-08-15 17:58:33 +10:00
|
|
|
end
|
|
|
|
|
|
2017-02-17 11:02:33 -05:00
|
|
|
# keep this rule here as a safeguard
|
|
|
|
|
# under no conditions to suspended or inactive accounts get current_user
|
|
|
|
|
current_user = nil if current_user && (current_user.suspended? || !current_user.active)
|
|
|
|
|
|
2018-07-18 16:04:57 +01:00
|
|
|
if current_user && should_update_last_seen?
|
2020-03-11 17:42:56 +11:00
|
|
|
ip = request.ip
|
2022-05-03 08:50:56 +10:00
|
|
|
user_id = current_user.id
|
|
|
|
|
old_ip = current_user.ip_address
|
2020-03-11 17:42:56 +11:00
|
|
|
|
2018-07-18 16:04:57 +01:00
|
|
|
Scheduler::Defer.later "Updating Last Seen" do
|
2022-05-03 08:50:56 +10:00
|
|
|
if User.should_update_last_seen?(user_id)
|
|
|
|
|
if u = User.find_by(id: user_id)
|
|
|
|
|
u.update_last_seen!(Time.zone.now, force: true)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
User.update_ip_address!(user_id, new_ip: ip, old_ip: old_ip)
|
2018-07-18 16:04:57 +01:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2013-10-09 15:10:37 +11:00
|
|
|
@env[CURRENT_USER_KEY] = current_user
|
|
|
|
|
end
|
|
|
|
|
|
2021-11-17 23:27:30 +03:00
|
|
|
def refresh_session(user, session, cookie_jar)
|
2017-01-31 17:21:37 -05:00
|
|
|
# if user was not loaded, no point refreshing session
|
|
|
|
|
# it could be an anonymous path, this would add cost
|
|
|
|
|
return if is_api? || !@env.key?(CURRENT_USER_KEY)
|
|
|
|
|
|
2017-02-17 11:02:33 -05:00
|
|
|
if !is_user_api? && @user_token && @user_token.user == user
|
2017-01-31 17:21:37 -05:00
|
|
|
rotated_at = @user_token.rotated_at
|
|
|
|
|
|
|
|
|
|
needs_rotation =
|
2023-01-09 12:10:19 +00:00
|
|
|
(
|
2017-01-31 17:21:37 -05:00
|
|
|
if @user_token.auth_token_seen
|
|
|
|
|
rotated_at < UserAuthToken::ROTATE_TIME.ago
|
2023-01-09 12:10:19 +00:00
|
|
|
else
|
2017-01-31 17:21:37 -05:00
|
|
|
rotated_at < UserAuthToken::URGENT_ROTATE_TIME.ago
|
2023-01-09 12:10:19 +00:00
|
|
|
end
|
|
|
|
|
)
|
2017-01-31 17:21:37 -05:00
|
|
|
|
2018-05-04 11:11:44 +10:00
|
|
|
if needs_rotation
|
2017-01-31 17:21:37 -05:00
|
|
|
if @user_token.rotate!(
|
|
|
|
|
user_agent: @env["HTTP_USER_AGENT"],
|
2017-03-07 13:27:34 -05:00
|
|
|
client_ip: @request.ip,
|
|
|
|
|
path: @env["REQUEST_PATH"],
|
|
|
|
|
)
|
2021-11-17 23:27:30 +03:00
|
|
|
set_auth_cookie!(@user_token.unhashed_auth_token, user, cookie_jar)
|
2020-04-14 06:32:24 -10:00
|
|
|
DiscourseEvent.trigger(:user_session_refreshed, user)
|
2017-01-31 17:21:37 -05:00
|
|
|
end
|
|
|
|
|
end
|
2016-07-25 12:07:31 +10:00
|
|
|
end
|
2017-01-31 17:21:37 -05:00
|
|
|
|
2021-11-17 23:27:30 +03:00
|
|
|
cookie_jar.delete(TOKEN_COOKIE) if !user && cookie_jar.key?(TOKEN_COOKIE)
|
2016-07-25 12:07:31 +10:00
|
|
|
end
|
|
|
|
|
|
2021-11-17 23:27:30 +03:00
|
|
|
def log_on_user(user, session, cookie_jar, opts = {})
|
2022-07-04 17:01:19 +02:00
|
|
|
@env[USER_TOKEN_KEY] = @user_token =
|
|
|
|
|
UserAuthToken.generate!(
|
2018-10-26 00:29:28 +02:00
|
|
|
user_id: user.id,
|
|
|
|
|
user_agent: @env["HTTP_USER_AGENT"],
|
|
|
|
|
path: @env["REQUEST_PATH"],
|
|
|
|
|
client_ip: @request.ip,
|
2018-11-12 16:34:12 +02:00
|
|
|
staff: user.staff?,
|
2018-11-12 16:00:12 +01:00
|
|
|
impersonate: opts[:impersonate],
|
|
|
|
|
)
|
2016-07-26 11:37:41 +10:00
|
|
|
|
2021-11-17 23:27:30 +03:00
|
|
|
set_auth_cookie!(@user_token.unhashed_auth_token, user, cookie_jar)
|
2020-03-17 16:48:24 +01:00
|
|
|
user.unstage!
|
2013-11-02 10:25:43 +11:00
|
|
|
make_developer_admin(user)
|
2016-04-26 22:38:19 +05:30
|
|
|
enable_bootstrap_mode(user)
|
2019-11-27 12:39:31 +00:00
|
|
|
|
|
|
|
|
UserAuthToken.enforce_session_count_limit!(user.id)
|
|
|
|
|
|
2013-10-09 15:10:37 +11:00
|
|
|
@env[CURRENT_USER_KEY] = user
|
|
|
|
|
end
|
|
|
|
|
|
2021-11-17 23:27:30 +03:00
|
|
|
def set_auth_cookie!(unhashed_auth_token, user, cookie_jar)
|
|
|
|
|
data = {
|
|
|
|
|
token: unhashed_auth_token,
|
|
|
|
|
user_id: user.id,
|
|
|
|
|
trust_level: user.trust_level,
|
|
|
|
|
issued_at: Time.zone.now.to_i,
|
2016-10-17 12:11:15 +11:00
|
|
|
}
|
2017-02-23 12:01:28 -05:00
|
|
|
|
2021-11-17 23:27:30 +03:00
|
|
|
expires = SiteSetting.maximum_session_age.hours.from_now if SiteSetting.persistent_sessions
|
2020-09-11 15:11:13 +10:00
|
|
|
|
2017-02-23 12:01:28 -05:00
|
|
|
same_site = SiteSetting.same_site_cookies if SiteSetting.same_site_cookies != "Disabled"
|
|
|
|
|
|
2021-11-17 23:27:30 +03:00
|
|
|
cookie_jar.encrypted[TOKEN_COOKIE] = {
|
|
|
|
|
value: data,
|
|
|
|
|
httponly: true,
|
|
|
|
|
secure: SiteSetting.force_https,
|
|
|
|
|
expires: expires,
|
|
|
|
|
same_site: same_site,
|
|
|
|
|
}
|
2016-10-17 12:11:15 +11:00
|
|
|
end
|
|
|
|
|
|
2022-10-06 15:16:38 +10:00
|
|
|
# This is also used to set the first admin of the site via
|
|
|
|
|
# the finish installation & register -> user account activation
|
|
|
|
|
# for signup flow, since all admin emails are stored in
|
|
|
|
|
# DISCOURSE_DEVELOPER_EMAILS for self-hosters.
|
2013-11-02 10:25:43 +11:00
|
|
|
def make_developer_admin(user)
|
|
|
|
|
if user.active? && !user.admin && Rails.configuration.respond_to?(:developer_emails) &&
|
|
|
|
|
Rails.configuration.developer_emails.include?(user.email)
|
2014-03-24 18:03:39 +11:00
|
|
|
user.admin = true
|
|
|
|
|
user.save
|
2022-10-06 15:16:38 +10:00
|
|
|
Group.refresh_automatic_groups!(:staff, :admins)
|
2013-11-02 10:25:43 +11:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2016-04-26 22:38:19 +05:30
|
|
|
def enable_bootstrap_mode(user)
|
2018-05-13 17:00:02 +02:00
|
|
|
return if SiteSetting.bootstrap_mode_enabled
|
|
|
|
|
|
|
|
|
|
if user.admin && user.last_seen_at.nil? && user.is_singular_admin?
|
|
|
|
|
Jobs.enqueue(:enable_bootstrap_mode, user_id: user.id)
|
|
|
|
|
end
|
2016-04-26 22:38:19 +05:30
|
|
|
end
|
|
|
|
|
|
2021-11-17 23:27:30 +03:00
|
|
|
def log_off_user(session, cookie_jar)
|
2017-01-31 17:21:37 -05:00
|
|
|
user = current_user
|
2018-05-13 17:00:02 +02:00
|
|
|
|
2017-01-31 17:21:37 -05:00
|
|
|
if SiteSetting.log_out_strict && user
|
|
|
|
|
user.user_auth_tokens.destroy_all
|
2016-05-18 17:27:54 +10:00
|
|
|
|
|
|
|
|
if user.admin && defined?(Rack::MiniProfiler)
|
|
|
|
|
# clear the profiling cookie to keep stuff tidy
|
2021-11-17 23:27:30 +03:00
|
|
|
cookie_jar.delete("__profilin")
|
2016-05-18 17:27:54 +10:00
|
|
|
end
|
|
|
|
|
|
2016-07-04 17:20:30 +08:00
|
|
|
user.logged_out
|
2017-01-31 17:21:37 -05:00
|
|
|
elsif user && @user_token
|
|
|
|
|
@user_token.destroy
|
2015-01-28 12:56:25 +11:00
|
|
|
end
|
2017-08-31 12:06:56 +08:00
|
|
|
|
2021-11-17 23:27:30 +03:00
|
|
|
cookie_jar.delete("authentication_data")
|
|
|
|
|
cookie_jar.delete(TOKEN_COOKIE)
|
2013-10-09 15:10:37 +11:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# api has special rights return true if api was detected
|
|
|
|
|
def is_api?
|
|
|
|
|
current_user
|
2016-12-16 12:05:20 +11:00
|
|
|
!!(@env[API_KEY_ENV])
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def is_user_api?
|
|
|
|
|
current_user
|
|
|
|
|
!!(@env[USER_API_KEY_ENV])
|
2013-10-09 15:10:37 +11:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def has_auth_cookie?
|
2021-11-17 23:27:30 +03:00
|
|
|
find_auth_token.present?
|
2013-10-09 15:10:37 +11:00
|
|
|
end
|
2014-05-23 08:13:25 +10:00
|
|
|
|
|
|
|
|
def should_update_last_seen?
|
2020-07-21 13:43:28 +08:00
|
|
|
return false unless can_write?
|
2019-01-22 18:07:48 +08:00
|
|
|
|
2021-11-17 23:27:30 +03:00
|
|
|
api = !!@env[API_KEY_ENV] || !!@env[USER_API_KEY_ENV]
|
2019-04-15 17:34:34 +01:00
|
|
|
|
|
|
|
|
if @request.xhr? || api
|
2020-03-26 17:35:32 +11:00
|
|
|
@env["HTTP_DISCOURSE_PRESENT"] == "true"
|
2017-02-28 12:34:57 -05:00
|
|
|
else
|
|
|
|
|
true
|
|
|
|
|
end
|
2014-05-23 08:13:25 +10:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
protected
|
|
|
|
|
|
|
|
|
|
def lookup_api_user(api_key_value, request)
|
2019-12-12 11:45:00 +00:00
|
|
|
if api_key = ApiKey.active.with_key(api_key_value).includes(:user).first
|
2019-03-12 17:16:42 -06:00
|
|
|
api_username = header_api_key? ? @env[HEADER_API_USERNAME] : request[API_USERNAME]
|
2019-11-08 17:28:48 -07:00
|
|
|
|
2021-12-29 12:22:21 +00:00
|
|
|
return nil if !api_key.request_allowed?(@env)
|
2014-11-20 15:21:49 +11:00
|
|
|
|
2019-09-03 18:10:29 +10:00
|
|
|
user =
|
|
|
|
|
if api_key.user
|
|
|
|
|
api_key.user if !api_username || (api_key.user.username_lower == api_username.downcase)
|
|
|
|
|
elsif api_username
|
|
|
|
|
User.find_by(username_lower: api_username.downcase)
|
|
|
|
|
elsif user_id = header_api_key? ? @env[HEADER_API_USER_ID] : request["api_user_id"]
|
|
|
|
|
User.find_by(id: user_id.to_i)
|
|
|
|
|
elsif external_id =
|
|
|
|
|
header_api_key? ? @env[HEADER_API_USER_EXTERNAL_ID] : request["api_user_external_id"]
|
|
|
|
|
SingleSignOnRecord.find_by(external_id: external_id.to_s).try(:user)
|
|
|
|
|
end
|
|
|
|
|
|
2020-07-21 13:43:28 +08:00
|
|
|
if user && can_write?
|
2022-04-06 11:01:52 -03:00
|
|
|
Scheduler::Defer.later "Updating api_key last_used" do
|
|
|
|
|
api_key.update_last_used!
|
|
|
|
|
end
|
2014-05-23 08:13:25 +10:00
|
|
|
end
|
2019-09-03 18:10:29 +10:00
|
|
|
|
|
|
|
|
user
|
2014-05-23 08:13:25 +10:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2018-09-04 16:35:49 +08:00
|
|
|
private
|
|
|
|
|
|
2020-08-24 10:24:52 +01:00
|
|
|
def parameter_api_patterns
|
|
|
|
|
PARAMETER_API_PATTERNS + DiscoursePluginRegistry.api_parameter_routes
|
|
|
|
|
end
|
|
|
|
|
|
2020-05-12 13:35:36 +01:00
|
|
|
# By default we only allow headers for sending API credentials
|
|
|
|
|
# However, in some scenarios it is essential to send them via url parameters
|
|
|
|
|
# so we need to add some exceptions
|
|
|
|
|
def api_parameter_allowed?
|
2020-10-06 17:20:15 +01:00
|
|
|
parameter_api_patterns.any? { |p| p.match?(env: @env) }
|
2020-03-16 12:05:24 -06:00
|
|
|
end
|
|
|
|
|
|
2019-03-12 17:16:42 -06:00
|
|
|
def header_api_key?
|
|
|
|
|
!!@env[HEADER_API_KEY]
|
|
|
|
|
end
|
|
|
|
|
|
2021-11-17 23:27:30 +03:00
|
|
|
def can_write?
|
|
|
|
|
@can_write ||= !Discourse.pg_readonly_mode?
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def admin_api_key_limiter
|
|
|
|
|
return @admin_api_key_limiter if @admin_api_key_limiter
|
2018-09-04 16:35:49 +08:00
|
|
|
|
2021-06-03 10:52:43 +01:00
|
|
|
limit = GlobalSetting.max_admin_api_reqs_per_minute.to_i
|
|
|
|
|
if GlobalSetting.respond_to?(:max_admin_api_reqs_per_key_per_minute)
|
2021-11-12 08:52:59 -06:00
|
|
|
Discourse.deprecate(
|
|
|
|
|
"DISCOURSE_MAX_ADMIN_API_REQS_PER_KEY_PER_MINUTE is deprecated. Please use DISCOURSE_MAX_ADMIN_API_REQS_PER_MINUTE",
|
|
|
|
|
drop_from: "2.9.0",
|
|
|
|
|
)
|
2021-11-17 23:27:30 +03:00
|
|
|
limit = [GlobalSetting.max_admin_api_reqs_per_key_per_minute.to_i, limit].max
|
2021-06-03 10:52:43 +01:00
|
|
|
end
|
2021-11-17 23:27:30 +03:00
|
|
|
@admin_api_key_limiter =
|
|
|
|
|
RateLimiter.new(nil, "admin_api_min", limit, 60, error_code: "admin_api_key_rate_limit")
|
|
|
|
|
end
|
2021-06-03 10:52:43 +01:00
|
|
|
|
2021-11-17 23:27:30 +03:00
|
|
|
def user_api_key_limiter_60_secs
|
|
|
|
|
@user_api_key_limiter_60_secs ||=
|
|
|
|
|
RateLimiter.new(
|
|
|
|
|
nil,
|
|
|
|
|
"user_api_min_#{@hashed_user_api_key}",
|
|
|
|
|
GlobalSetting.max_user_api_reqs_per_minute,
|
|
|
|
|
60,
|
|
|
|
|
error_code: "user_api_key_limiter_60_secs",
|
|
|
|
|
)
|
2018-09-04 16:35:49 +08:00
|
|
|
end
|
|
|
|
|
|
2021-11-17 23:27:30 +03:00
|
|
|
def user_api_key_limiter_1_day
|
|
|
|
|
@user_api_key_limiter_1_day ||=
|
|
|
|
|
RateLimiter.new(
|
|
|
|
|
nil,
|
|
|
|
|
"user_api_day_#{@hashed_user_api_key}",
|
|
|
|
|
GlobalSetting.max_user_api_reqs_per_day,
|
|
|
|
|
86_400,
|
|
|
|
|
error_code: "user_api_key_limiter_1_day",
|
|
|
|
|
)
|
2020-07-21 13:43:28 +08:00
|
|
|
end
|
|
|
|
|
|
2021-11-17 23:27:30 +03:00
|
|
|
def find_auth_token
|
|
|
|
|
return @auth_token if defined?(@auth_token)
|
|
|
|
|
|
|
|
|
|
@auth_token =
|
|
|
|
|
begin
|
|
|
|
|
if v0 = self.class.find_v0_auth_cookie(@request)
|
|
|
|
|
v0
|
|
|
|
|
elsif v1 = self.class.find_v1_auth_cookie(@env)
|
|
|
|
|
v1[:token] if v1[:issued_at] >= SiteSetting.maximum_session_age.hours.ago.to_i
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
2013-10-09 15:10:37 +11:00
|
|
|
end
|