412 lines
11 KiB
Ruby
412 lines
11 KiB
Ruby
|
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
|