مراية لـ
https://github.com/postalserver/postal.git
تم المزامنة 2025-11-30 21:32:30 +00:00
feat: openid connect support (#2873)
هذا الالتزام موجود في:
@@ -5,15 +5,20 @@ module HasAuthentication
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
has_secure_password
|
||||
has_secure_password validations: false
|
||||
|
||||
validates :password, length: { minimum: 8, allow_blank: true }
|
||||
validates :password, confirmation: { allow_blank: true }
|
||||
validate :validate_password_presence
|
||||
|
||||
before_save :clear_password_reset_token_on_password_change
|
||||
|
||||
scope :with_password, -> { where.not(password_digest: nil) }
|
||||
end
|
||||
|
||||
class_methods do
|
||||
def authenticate(email_address, password)
|
||||
user = where(email_address: email_address).first
|
||||
user = find_by(email_address: email_address)
|
||||
raise Postal::Errors::AuthenticationError, "InvalidEmailAddress" if user.nil?
|
||||
raise Postal::Errors::AuthenticationError, "InvalidPassword" unless user.authenticate(password)
|
||||
|
||||
@@ -30,6 +35,10 @@ module HasAuthentication
|
||||
end
|
||||
|
||||
def begin_password_reset(return_to = nil)
|
||||
if Postal::Config.oidc.enabled? && (oidc_uid.present? || password_digest.blank?)
|
||||
raise Postal::Error, "User has OIDC enabled, password resets are not supported"
|
||||
end
|
||||
|
||||
self.password_reset_token = SecureRandom.alphanumeric(24)
|
||||
self.password_reset_token_valid_until = 1.day.from_now
|
||||
save!
|
||||
@@ -45,6 +54,12 @@ module HasAuthentication
|
||||
self.password_reset_token_valid_until = nil
|
||||
end
|
||||
|
||||
def validate_password_presence
|
||||
return if password_digest.present? || Postal::Config.oidc.enabled?
|
||||
|
||||
errors.add :password, :blank
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# -*- SkipSchemaAnnotations
|
||||
|
||||
@@ -5,19 +5,21 @@
|
||||
# Table name: users
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# uuid :string(255)
|
||||
# first_name :string(255)
|
||||
# last_name :string(255)
|
||||
# admin :boolean default(FALSE)
|
||||
# email_address :string(255)
|
||||
# password_digest :string(255)
|
||||
# time_zone :string(255)
|
||||
# email_verification_token :string(255)
|
||||
# email_verified_at :datetime
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# first_name :string(255)
|
||||
# last_name :string(255)
|
||||
# oidc_issuer :string(255)
|
||||
# oidc_uid :string(255)
|
||||
# password_digest :string(255)
|
||||
# password_reset_token :string(255)
|
||||
# password_reset_token_valid_until :datetime
|
||||
# admin :boolean default(FALSE)
|
||||
# time_zone :string(255)
|
||||
# uuid :string(255)
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
@@ -28,13 +30,11 @@
|
||||
class User < ApplicationRecord
|
||||
|
||||
include HasUUID
|
||||
|
||||
include HasAuthentication
|
||||
|
||||
validates :first_name, presence: true
|
||||
validates :last_name, presence: true
|
||||
validates :email_address, presence: true, uniqueness: { case_sensitive: false }, format: { with: /@/, allow_blank: true }
|
||||
validates :time_zone, presence: true
|
||||
|
||||
default_value :time_zone, -> { "UTC" }
|
||||
|
||||
@@ -53,24 +53,85 @@ class User < ApplicationRecord
|
||||
"#{first_name} #{last_name}"
|
||||
end
|
||||
|
||||
def password?
|
||||
password_digest.present?
|
||||
end
|
||||
|
||||
def oidc?
|
||||
oidc_uid.present?
|
||||
end
|
||||
|
||||
def to_param
|
||||
uuid
|
||||
end
|
||||
|
||||
def md5_for_gravatar
|
||||
@md5_for_gravatar ||= Digest::MD5.hexdigest(email_address.to_s.downcase)
|
||||
end
|
||||
|
||||
def avatar_url
|
||||
@avatar_url ||= email_address ? "https://secure.gravatar.com/avatar/#{md5_for_gravatar}?rating=PG&size=120&d=mm" : nil
|
||||
end
|
||||
|
||||
def email_tag
|
||||
"#{name} <#{email_address}>"
|
||||
end
|
||||
|
||||
def self.[](email)
|
||||
where(email_address: email).first
|
||||
class << self
|
||||
|
||||
# Lookup a user by email address
|
||||
#
|
||||
# @param email [String] the email address
|
||||
#
|
||||
# @return [User, nil] the user
|
||||
def [](email)
|
||||
find_by(email_address: email)
|
||||
end
|
||||
|
||||
# Find a user based on an OIDC authentication hash
|
||||
#
|
||||
# @param auth [Hash] the authentication hash
|
||||
# @param logger [Logger] a logger to log debug information to
|
||||
#
|
||||
# @return [User, nil] the user
|
||||
def find_from_oidc(auth, logger: nil)
|
||||
config = Postal::Config.oidc
|
||||
|
||||
uid = auth[config.uid_field]
|
||||
oidc_name = auth[config.name_field]
|
||||
oidc_email_address = auth[config.email_address_field]
|
||||
|
||||
logger&.debug "got auth details from issuer: #{auth.inspect}"
|
||||
|
||||
# look for an existing user with the same UID and OIDC issuer. If we find one,
|
||||
# this is the user we'll want to use.
|
||||
user = where(oidc_uid: uid, oidc_issuer: config.issuer).first
|
||||
|
||||
if user
|
||||
logger&.debug "found user with UID #{uid} for issuer #{config.issuer} (user ID: #{user.id})"
|
||||
else
|
||||
logger&.debug "no user with UID #{uid} for issuer #{config.issuer}"
|
||||
end
|
||||
|
||||
# if we don't have an existing user, we will look for users which have no OIDC
|
||||
# credentials but with a matching e-mail address.
|
||||
if user.nil? && oidc_email_address.present?
|
||||
user = where(oidc_uid: nil, email_address: oidc_email_address).first
|
||||
if user
|
||||
logger&.debug "found user with e-mail address #{oidc_email_address} (user ID: #{user.id})"
|
||||
else
|
||||
logger&.debug "no user with e-mail address #{oidc_email_address}"
|
||||
end
|
||||
end
|
||||
|
||||
# now, if we still don't have a user, we're not going to create one so we'll just
|
||||
# return nil (we might auto create users in the future but not right now)
|
||||
return if user.nil?
|
||||
|
||||
# otherwise, let's update our user as appropriate
|
||||
user.oidc_uid = uid
|
||||
user.oidc_issuer = config.issuer
|
||||
user.email_address = oidc_email_address if oidc_email_address.present?
|
||||
user.first_name, user.last_name = oidc_name.split(/\s+/, 2) if oidc_name.present?
|
||||
user.password = nil
|
||||
user.save!
|
||||
|
||||
# return the user
|
||||
user
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
المرجع في مشكلة جديدة
حظر مستخدم