cartodb-4.42/config/initializers/warden.rb

412 lines
11 KiB
Ruby
Raw Normal View History

2024-04-06 13:25:13 +08:00
require 'base64'
require 'securerandom'
require_dependency 'carto/user_authenticator'
require_dependency 'carto/email_cleaner'
require_dependency 'carto/errors'
Rails.configuration.middleware.use RailsWarden::Manager do |manager|
manager.default_strategies :password, :api_authentication
manager.failure_app = SessionsController
end
# All strategies should:
# - Include this module
# - Override the methods as needed
module CartoStrategy
include ::LoggerHelper
def affected_by_password_expiration?
true
end
def affected_by_reset_password_locked?
false
end
def check_password_expired(user)
if affected_by_password_expiration? && user.password_expired?
throw(:warden, action: :password_change, username: user.username)
end
end
def reset_password_rate_limit(user)
user.reset_password_rate_limit if affected_by_reset_password_locked?
end
def trigger_login_event(user)
check_password_expired(user)
reset_password_rate_limit(user)
CartoGearsApi::Events::EventManager.instance.notify(CartoGearsApi::Events::UserLoginEvent.new(user))
# From the very beginning it's been assumed that after login you go to the dashboard, and
# we're using that event as a synonymous to "last logged in date". Now you can skip dashboard
# after login (see #11946), so marking that event on authentication is more accurate with the
# meaning (although not with the name).
user.view_dashboard
begin
user.update_in_central
rescue StandardError => e
log_warning(message: "Error updating lastlogin_date in central", exception: e)
end
end
end
# Setup Session Serialization
class Warden::SessionSerializer
def serialize(user)
user.username
end
def deserialize(username)
::User.filter(username: username).first
end
end
Warden::Strategies.add(:password) do
include Carto::UserAuthenticator
include Carto::EmailCleaner
include CartoStrategy
def affected_by_reset_password_locked?
true
end
def valid_password_strategy_for_user(user)
user.organization.nil? || user.organization.auth_username_password_enabled
end
def authenticate!
if params[:email] && params[:password]
if (user = authenticate(clean_email(params[:email]), params[:password]))
if user.enabled? && valid_password_strategy_for_user(user)
trigger_login_event(user)
success!(user, :message => "Success")
request.flash['logged'] = true
elsif !user.enable_account_token.nil?
throw(:warden, :action => 'account_token_authentication_error', :user_id => user.id)
else
fail!
end
else
fail!
end
else
fail!
end
end
end
Warden::Strategies.add(:enable_account_token) do
include CartoStrategy
def authenticate!
if params[:id]
user = ::User.where(enable_account_token: params[:id]).first
if user
user.enable_account_token = nil
user.save
trigger_login_event(user)
success!(user)
else
fail!
end
else
fail!
end
end
end
Warden::Strategies.add(:oauth) do
include CartoStrategy
def valid_oauth_strategy_for_user(user)
user.organization.nil? || user.organization.auth_github_enabled
end
def authenticate!
fail! unless env[:oauth_api]
oauth_api = env[:oauth_api]
user = oauth_api.user
if user && oauth_api.config.valid_method_for?(user)
trigger_login_event(user)
success!(user)
else
fail!
end
end
end
Warden::Strategies.add(:ldap) do
include CartoStrategy
def affected_by_password_expiration?
false
end
def authenticate!
(fail! and return) if (params[:email].blank? || params[:password].blank?)
user = nil
begin
user = Carto::Ldap::Manager.new.authenticate(params[:email], params[:password])
rescue Carto::Ldap::LDAPUserNotPresentAtCartoDBError => exception
throw(:warden, action: 'ldap_user_not_at_cartodb',
cartodb_username: exception.cartodb_username, organization_id: exception.organization_id,
ldap_username: exception.ldap_username, ldap_email: exception.ldap_email)
end
# Fails, but do not stop processin other strategies (allows fallbacks)
return unless user
trigger_login_event(user)
success!(user, :message => "Success")
request.flash['logged'] = true
end
end
Warden::Strategies.add(:api_authentication) do
include CartoStrategy
def affected_by_password_expiration?
false
end
def authenticate!
# WARNING: The following code is a modified copy of the oauth10_token method from
# oauth-plugin-0.4.0.pre4/lib/oauth/controllers/application_controller_methods.rb
# It also checks token class like does the oauth10_access_token method of that same file
if ClientApplication.verify_request(request) do |request_proxy|
@oauth_token = ClientApplication.find_token(request_proxy.token)
if @oauth_token.respond_to?(:provided_oauth_verifier=)
@oauth_token.provided_oauth_verifier=request_proxy.oauth_verifier
end
# return the token secret and the consumer secret
[(@oauth_token.nil? ? nil : @oauth_token.secret), (@oauth_token.nil? || @oauth_token.client_application.nil? ? nil : @oauth_token.client_application.secret)]
end
if @oauth_token && @oauth_token.is_a?(Carto::AccessToken)
user = ::User.find_with_custom_fields(@oauth_token.user_id)
if user.enable_account_token.nil?
success!(user) and return
else
throw(:warden, :action => 'account_token_authentication_error', :user_id => user.id)
end
end
end
fail!
end
end
Warden::Strategies.add(:http_header_authentication) do
include CartoStrategy
def affected_by_password_expiration?
false
end
def valid?
Carto::HttpHeaderAuthentication.new.valid?(request)
end
def authenticate!
user = Carto::HttpHeaderAuthentication.new.get_user(request)
return fail! unless user.present?
trigger_login_event(user)
success!(user)
rescue StandardError => e
CartoDB.report_exception(e, "Authenticating with http_header_authentication", user: user)
return fail!
end
end
Warden::Strategies.add(:saml) do
include CartoStrategy
include Carto::EmailCleaner
include ::LoggerHelper
def affected_by_password_expiration?
false
end
def organization_from_request
subdomain = CartoDB.extract_subdomain(request)
Carto::Organization.where(name: subdomain).first if subdomain
end
def saml_service(organization = organization_from_request)
Carto::SamlService.new(organization) if organization
end
def valid?
params[:SAMLResponse].present? && saml_service.try(:enabled?)
end
def authenticate!
organization = organization_from_request
saml_service = Carto::SamlService.new(organization)
email = clean_email(saml_service.get_user_email(params[:SAMLResponse]))
user = organization.users.where(email: email).first
if user
if user.try(:enabled?)
trigger_login_event(user)
success!(user, message: "Success")
request.flash['logged'] = true
else
fail!
end
else
throw(:warden,
action: 'saml_user_not_in_carto',
organization_id: organization.id,
saml_email: email)
end
rescue StandardError => e
log_error(message: "Authenticating with SAML", exception: e)
return fail!
end
end
# @see ApplicationController.update_session_security_token
Warden::Manager.after_set_user except: :fetch do |user, auth, opts|
auth.session(opts[:scope])[:skip_multifactor_authentication] = auth.winning_strategy && !auth.winning_strategy.store?
auth.session(opts[:scope])[:sec_token] = user.security_token
# Only at the editor, and only after new authentications, destroy other sessions
# @see #4656
warden_proxy = auth.env['warden']
# On testing there is no warden global so we cannot run this logic
if warden_proxy
warden_sessions = auth.env['rack.session'].to_hash.select do |key, _|
key.start_with?("warden.user") && !key.end_with?(".session")
end
warden_sessions.each do |_, value|
unless value == user.username
warden_proxy.logout(value) if warden_proxy.authenticated?(value)
end
end
end
end
Warden::Manager.after_set_user do |user, auth, opts|
# Without winning strategy (loading cookie from session) assume we want to respect expired passwords
should_check_expiration = !auth.winning_strategy || auth.winning_strategy.affected_by_password_expiration?
throw(:warden, action: :password_expired) if should_check_expiration && user.password_expired?
end
Warden::Strategies.add(:user_creation) do
include CartoStrategy
def authenticate!
username = params[:username]
user = ::User.where(username: username).first
return fail! unless user
user_creation = Carto::UserCreation.where(user_id: user.id).first
return fail! unless user_creation
if user_creation.autologin?
trigger_login_event(user)
success!(user, :message => "Success")
else
fail!
end
end
end
module Carto::Api::AuthApiAuthentication
include CartoStrategy
# We don't want to store a session and send a response cookie
def store?
false
end
def affected_by_password_expiration?
false
end
def valid?
base64_auth.present? || params[:api_key].present?
end
def base64_auth
match = AUTH_HEADER_RE.match(request.headers['Authorization'])
match && match[:auth]
end
def authenticate_user(require_master_key)
user, token = user_and_token_from_request
return fail! unless user && token
api_key = user.api_keys.where(token: token)
api_key = require_master_key ? api_key.master : api_key
return fail! unless api_key.exists?
success!(user)
rescue StandardError
fail!
end
def request_api_key
return @request_api_key if @request_api_key
user, token = user_and_token_from_request
@request_api_key = user.api_keys.where(token: token).first if user && token
# If user is logged in though other means, assume a master key
@request_api_key = current_user.api_keys.master.first if !@request_api_key && current_user
@request_api_key
end
private
AUTH_HEADER_RE = /basic\s(?<auth>\w+)/i
def user_and_token_from_request
return unless valid?
if base64_auth.present?
username, token = split_auth
return unless username == CartoDB.extract_subdomain(request)
elsif params[:api_key]
token = params[:api_key]
username = CartoDB.extract_subdomain(request)
end
[User[username: username], token]
end
def split_auth
decoded_auth = Base64.decode64(base64_auth)
decoded_auth.split(':')
end
end
Warden::Strategies.add(:auth_api) do
include Carto::Api::AuthApiAuthentication
def authenticate!
authenticate_user(true)
end
end
Warden::Strategies.add(:any_auth_api) do
include Carto::Api::AuthApiAuthentication
def authenticate!
authenticate_user(false)
end
end