مراية لـ
https://github.com/postalserver/postal.git
تم المزامنة 2025-11-30 21:32:30 +00:00
feat: openid connect support (#2873)
هذا الالتزام موجود في:
@@ -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
|
||||
#
|
||||
|
||||
27
spec/models/user/authentication_spec.rb
Normal file
27
spec/models/user/authentication_spec.rb
Normal file
@@ -0,0 +1,27 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe User do
|
||||
describe ".authenticate" do
|
||||
it "does not authenticate users with invalid emails" do
|
||||
expect { User.authenticate("nothing@nothing.com", "hello") }.to raise_error(Postal::Errors::AuthenticationError) do |e|
|
||||
expect(e.error).to eq "InvalidEmailAddress"
|
||||
end
|
||||
end
|
||||
|
||||
it "does not authenticate users with invalid passwords" do
|
||||
user = create(:user)
|
||||
expect { User.authenticate(user.email_address, "hello") }.to raise_error(Postal::Errors::AuthenticationError) do |e|
|
||||
expect(e.error).to eq "InvalidPassword"
|
||||
end
|
||||
end
|
||||
|
||||
it "authenticates valid users" do
|
||||
user = create(:user)
|
||||
auth_user = nil
|
||||
expect { auth_user = User.authenticate(user.email_address, "passw0rd") }.to_not raise_error
|
||||
expect(auth_user).to eq user
|
||||
end
|
||||
end
|
||||
end
|
||||
115
spec/models/user/oidc_spec.rb
Normal file
115
spec/models/user/oidc_spec.rb
Normal file
@@ -0,0 +1,115 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe User do
|
||||
let(:user) { build(:user) }
|
||||
|
||||
describe "#oidc?" do
|
||||
it "returns true if the user has an OIDC UID" do
|
||||
user.oidc_uid = "123"
|
||||
expect(user.oidc?).to be true
|
||||
end
|
||||
|
||||
it "returns false if the user does not have an OIDC UID" do
|
||||
user.oidc_uid = nil
|
||||
expect(user.oidc?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe ".find_from_oidc" do
|
||||
let(:issuer) { "https://identity.example.com" }
|
||||
|
||||
before do
|
||||
allow(Postal::Config.oidc).to receive(:enabled?).and_return(true)
|
||||
allow(Postal::Config.oidc).to receive(:issuer).and_return(issuer)
|
||||
allow(Postal::Config.oidc).to receive(:email_address_field).and_return("email")
|
||||
end
|
||||
|
||||
let(:uid) { "abcdef" }
|
||||
let(:oidc_name) { "John Smith" }
|
||||
let(:oidc_email) { "test@example.com" }
|
||||
|
||||
let(:auth) { { "sub" => uid, "email" => oidc_email, "name" => oidc_name } }
|
||||
let(:logger) { TestLogger.new }
|
||||
|
||||
subject(:result) { described_class.find_from_oidc(auth, logger: logger) }
|
||||
|
||||
context "when there is a user that matchers the UID and issuer" do
|
||||
before do
|
||||
@existing_user = create(:user, oidc_uid: uid, oidc_issuer: issuer, first_name: "mary",
|
||||
last_name: "apples", email_address: "mary@apples.com")
|
||||
end
|
||||
|
||||
it "returns that user" do
|
||||
expect(result).to eq @existing_user
|
||||
end
|
||||
|
||||
it "updates the name and email address" do
|
||||
result
|
||||
@existing_user.reload
|
||||
expect(@existing_user.first_name).to eq "John"
|
||||
expect(@existing_user.last_name).to eq "Smith"
|
||||
expect(@existing_user.email_address).to eq "test@example.com"
|
||||
end
|
||||
|
||||
it "logs" do
|
||||
result
|
||||
expect(logger).to have_logged(/found user with UID abcdef/i)
|
||||
end
|
||||
end
|
||||
|
||||
context "when there is no user which matches the UID and issuer" do
|
||||
context "when there is a user which matches the email address without an OIDC UID" do
|
||||
before do
|
||||
@existing_user = create(:user, first_name: "mary",
|
||||
last_name: "apples", email_address: "test@example.com")
|
||||
end
|
||||
|
||||
it "returns that user" do
|
||||
expect(result).to eq @existing_user
|
||||
end
|
||||
|
||||
it "adds the UID and issuer to the user" do
|
||||
result
|
||||
@existing_user.reload
|
||||
expect(@existing_user.oidc_uid).to eq uid
|
||||
expect(@existing_user.oidc_issuer).to eq issuer
|
||||
end
|
||||
|
||||
it "updates the name if changed" do
|
||||
result
|
||||
@existing_user.reload
|
||||
expect(@existing_user.first_name).to eq "John"
|
||||
expect(@existing_user.last_name).to eq "Smith"
|
||||
end
|
||||
|
||||
it "removes the password" do
|
||||
@existing_user.password = "password"
|
||||
@existing_user.save!
|
||||
result
|
||||
@existing_user.reload
|
||||
expect(@existing_user.password_digest).to be_nil
|
||||
end
|
||||
|
||||
it "logs" do
|
||||
result
|
||||
expect(logger).to have_logged(/no user with UID abcdef/)
|
||||
expect(logger).to have_logged(/found user with e-mail address test@example.com/)
|
||||
end
|
||||
end
|
||||
|
||||
context "when there is no user which matches the email address" do
|
||||
it "returns nil" do
|
||||
expect(result).to be_nil
|
||||
end
|
||||
|
||||
it "logs" do
|
||||
result
|
||||
expect(logger).to have_logged(/no user with UID abcdef/)
|
||||
expect(logger).to have_logged(/no user with e-mail address/)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -11,6 +11,8 @@
|
||||
# email_verified_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
|
||||
@@ -27,34 +29,105 @@
|
||||
require "rails_helper"
|
||||
|
||||
describe User do
|
||||
context "model" do
|
||||
subject(:user) { create(:user) }
|
||||
subject(:user) { build(:user) }
|
||||
|
||||
describe "validations" do
|
||||
it { is_expected.to validate_presence_of(:first_name) }
|
||||
it { is_expected.to validate_presence_of(:last_name) }
|
||||
it { is_expected.to validate_presence_of(:email_address) }
|
||||
it { is_expected.to validate_presence_of(:password) }
|
||||
it { is_expected.to validate_uniqueness_of(:email_address).case_insensitive }
|
||||
it { is_expected.to allow_value("test@example.com").for(:email_address) }
|
||||
it { is_expected.to allow_value("test@example.co.uk").for(:email_address) }
|
||||
it { is_expected.to allow_value("test+tagged@example.co.uk").for(:email_address) }
|
||||
it { is_expected.to allow_value("test+tagged@EXAMPLE.COM").for(:email_address) }
|
||||
it { is_expected.to_not allow_value("test+tagged").for(:email_address) }
|
||||
it { is_expected.to_not allow_value("test.com").for(:email_address) }
|
||||
|
||||
it "does not require a password when OIDC is enabled" do
|
||||
allow(Postal::Config.oidc).to receive(:enabled?).and_return(true)
|
||||
user.password = nil
|
||||
expect(user.save).to be true
|
||||
end
|
||||
end
|
||||
|
||||
describe "relationships" do
|
||||
it { is_expected.to have_many(:organization_users) }
|
||||
it { is_expected.to have_many(:organizations) }
|
||||
end
|
||||
|
||||
describe "creation" do
|
||||
before { user.save }
|
||||
|
||||
it "should have a UUID" do
|
||||
expect(user.uuid).to be_a String
|
||||
expect(user.uuid.length).to eq 36
|
||||
end
|
||||
|
||||
it "has a default timezone" do
|
||||
expect(user.time_zone).to eq "UTC"
|
||||
end
|
||||
end
|
||||
|
||||
context ".authenticate" do
|
||||
it "should not authenticate users with invalid emails" do
|
||||
expect { User.authenticate("nothing@nothing.com", "hello") }.to raise_error(Postal::Errors::AuthenticationError) do |e|
|
||||
expect(e.error).to eq "InvalidEmailAddress"
|
||||
describe "#organizations_scope" do
|
||||
context "when the user is an admin" do
|
||||
it "returns a scope of all organizations" do
|
||||
user.admin = true
|
||||
scope = user.organizations_scope
|
||||
expect(scope).to eq Organization.present
|
||||
end
|
||||
end
|
||||
|
||||
it "should not authenticate users with invalid passwords" do
|
||||
user = create(:user)
|
||||
expect { User.authenticate(user.email_address, "hello") }.to raise_error(Postal::Errors::AuthenticationError) do |e|
|
||||
expect(e.error).to eq "InvalidPassword"
|
||||
context "when the user not an admin" do
|
||||
it "returns a scope including only orgs the user is associated with" do
|
||||
user.admin = false
|
||||
user.organizations << create(:organization)
|
||||
scope = user.organizations_scope
|
||||
expect(scope).to eq user.organizations.present
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "should authenticate valid users" do
|
||||
describe "#name" do
|
||||
it "returns the name" do
|
||||
user.first_name = "John"
|
||||
user.last_name = "Doe"
|
||||
expect(user.name).to eq "John Doe"
|
||||
end
|
||||
end
|
||||
|
||||
describe "#password?" do
|
||||
it "returns true if the user has a password" do
|
||||
user.password = "password"
|
||||
expect(user.password?).to be true
|
||||
end
|
||||
|
||||
it "returns false if the user does not have a password" do
|
||||
user.password = nil
|
||||
expect(user.password?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe "#to_param" do
|
||||
it "returns the UUID" do
|
||||
user.uuid = "123"
|
||||
expect(user.to_param).to eq "123"
|
||||
end
|
||||
end
|
||||
|
||||
describe "#email_tag" do
|
||||
it "returns the name and email address" do
|
||||
user.first_name = "John"
|
||||
user.last_name = "Doe"
|
||||
user.email_address = "john@example.com"
|
||||
expect(user.email_tag).to eq "John Doe <john@example.com>"
|
||||
end
|
||||
end
|
||||
|
||||
describe ".[]" do
|
||||
it "should find a user by email address" do
|
||||
user = create(:user)
|
||||
auth_user = nil
|
||||
expect { auth_user = User.authenticate(user.email_address, "passw0rd") }.to_not raise_error
|
||||
expect(auth_user).to eq user
|
||||
expect(User[user.email_address]).to eq user
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
المرجع في مشكلة جديدة
حظر مستخدم