授权与多态性:构建安全且灵活的应用系统
在开发应用程序时,授权和多态性是确保系统安全性和灵活性的关键因素。本文将深入探讨如何在应用中实现授权和多态性,以及如何通过测试来验证这些功能的正确性。
1. 表单授权显示
在视图中,我们可能需要根据用户的权限来显示特定的表单字段。例如,在
Thing::Cell::Form
类中,我们通过检查合约对象的
options_for(:is_author)
方法来确定是否显示
is_author
字段:
class Thing::Cell::Form < ::Cell::Concept
def has_author_field?
contract.options_for(:is_author)
end
end
这种方式虽然将视图与合约紧密耦合,但在某些情况下是可行的。为了简化操作,我们可以在操作类中直接暴露一个
has_author_field?
读取器,或者引入一个上下文对象来反映这些设置。
2. 更新操作测试
在进行更新操作时,我们需要对不同用户角色进行测试。首先,定义不同用户类型的工厂方法:
class ThingUpdateTest < MiniTest::Spec
let (:current_user) { User::Create.(user: {email: "fred@trb.org"}).model }
let (:admin) { User::Create.(user: {email: "admin@trb.org"}).model }
let (:author) { User::Create.(user: {email: "solnic@trb.org"}).model }
end
然后,针对匿名用户、已登录用户和管理员分别编写测试用例。例如,对于匿名用户,尝试更新操作时应抛出
Trailblazer::NotAuthorizedError
异常:
describe "anonymous" do
let (:thing) { Thing::Create.(thing: {name: "Rails", description: "Kickass web dev", "users"=>[{"email"=>author.email}]}).model }
it do
assert_raises Trailblazer::NotAuthorizedError do
Thing::Update.(
id: thing.id,
thing: {name: "Rails", description: "Kickass web dev"}
)
end
end
end
对于已登录用户,测试更新操作时应忽略某些参数:
describe "signed-in" do
let (:thing) { Thing::Create.(thing: {name: "Rails", description: "Kickass web dev", users: ["email"=>author.email]}).model }
it "persists valid, ignores name, ignores is_author" do
Thing::Update.(
id: thing.id,
thing: {name: "Lotus", description: "MVC, well..", is_author: "1"},
current_user: author
).model
thing.reload
thing.name.must_equal "Rails"
thing.description.must_equal "MVC, well.."
thing.users.must_equal [author]
end
end
3. 测试副作用
在测试过程中,我们可以通过测试隐式副作用来验证操作的正确性,而不是直接测试内部实现。例如,通过注入不同的参数和用户角色,确保正确的操作实例化并使用正确的合约。
4. 编辑表单渲染测试
在测试编辑表单渲染时,我们需要考虑不同用户角色的情况。例如,对于匿名用户,尝试访问编辑表单时应被重定向到根页面:
class ThingsControllerUpdateTest < IntegrationTest
describe "#edit" do
# anonymous
it "doesn't work with not signed-in" do
visit "/things/#{thing.id}/edit"
page.current_path.must_equal "/"
end
end
end
对于已登录的作者,编辑表单应显示正确的信息:
describe "#edit" do
# signed-in
it do
sign_in!
visit "/things/#{thing.id}/edit"
page.wont_have_css "form.admin"
page.must_have_css "form #thing_name.readonly[value='Rails']"
page.must_have_css "#thing_users_attributes_0_email.readonly[value='fred@trb.org']"
assert_edit_form
end
end
5. 删除功能实现
随着测试数据的增加,我们需要实现删除功能来清理数据库。
Thing::Delete
类用于实现删除操作:
class Thing::Delete < Trailblazer::Operation
include CRUD
model Thing, :find
policy Thing::Policy, :delete?
def process(params)
model.destroy
delete_images!
expire_cache!
end
def delete_images!
Thing::ImageProcessor.new(model.image_meta_data).image! { |v| v.delete! }
end
include Gemgem::ExpireCache
end
在
Thing::Policy
类中,将
delete?
规则映射到
edit?
规则:
class Thing::Policy
def delete?
edit?
end
end
这样,只有管理员和作者才能删除事物。
6. 删除操作测试
在测试删除操作时,我们需要考虑不同用户角色和事物状态。例如,对于没有作者的事物,匿名用户和非作者的已登录用户尝试删除时应抛出
Trailblazer::NotAuthorizedError
异常:
class ThingDeleteTest < MiniTest::Spec
let (:current_user) { User::Create.(user: {email: "fred@trb.org"}).model }
let (:admin) { User::Create.(user: {email: "admin@trb.org"}).model }
describe "authorless" do
let (:thing) { Thing::Create.(thing: {name: "Rails"}).model }
# anonymous
it "can't be deleted" do
assert_raises Trailblazer::NotAuthorizedError do
Thing::Delete.(id: thing.id)
end
end
end
end
对于有作者的事物,作者和管理员可以成功删除,并且相关的图片文件也会被删除:
describe "with authors" do
it "deleted by author, with images and comments" do
thing = Thing::Create.(thing: {
name: "Rails", is_author: "1",
file: File.open("test/images/cells.jpg")
}, current_user: current_user).model
thing = Thing::Delete.(id: thing.id, current_user: current_user).model
thing.destroyed?.must_equal true
Paperdragon::Attachment.new(thing.image_meta_data).exists?.must_equal false
end
end
7. 模拟用户功能
为了方便测试和开发,我们可以实现模拟用户的功能。在
ApplicationController
中,将
Tyrant::Session::Setup
替换为
Tyrant::Session::Impersonate
:
class ApplicationController < ActionController::Base
before_filter { Tyrant::Session::Impersonate.(params.merge!(tyrant: tyrant)) }
end
当参数中包含
:as
键时,Tyrant 会根据邮箱查找新用户并替换
params[:current_user]
,同时设置
params[:real_user]
为原始用户。为了确保只有管理员可以使用模拟用户功能,我们可以创建自己的
Session::Impersonate
类:
class Session::Impersonate < Tyrant::Session::Impersonate
include Policy
policy Thing::Policy, :admin?
end
然后在
ApplicationController
中使用我们自己的实现:
class ApplicationController < ActionController::Base
before_filter { Session::Impersonate.(params.merge!(tyrant: tyrant)) }
end
8. 导航栏显示模拟用户信息
为了在导航栏中显示模拟用户的信息,我们需要扩展传递给导航单元格的参数:
%header
= concept "navigation/cell", OpenStruct.new(
real_user: params[:real_user],
current_user: params[:current_user],
"signed_in?" => tyrant.signed_in?
)
在
Navigation::Cell
类中,添加
real_user
属性,并扩展
welcome_signed_in
方法:
module Navigation
class Cell < ::Cell::Concept
property :current_user
property :real_user
def welcome_signed_in
link_to("#{impersonate_icon} Hi, #{current_user.email}".html_safe, user_path(current_user))
end
def impersonate_icon
return unless real_user
"<i data-tooltip class=\"fi-sheriff-badge\" title=\"You really are: #{real_user.email}\"></i>"
end
end
end
9. 模拟用户功能测试
在测试模拟用户功能时,我们需要考虑不同用户角色。例如,匿名用户尝试使用模拟用户功能时会被要求登录:
class SessionImpersonateTest < IntegrationTest
let (:jimmy) { User::Create.(user: {email: "jimmy@trb.org"}) }
before { jimmy }
it do
visit "/?as=jimmy@trb.org"
page.must_have_css "a", text: "Sign in" # not logged in.
end
end
管理员登录后可以成功模拟其他用户:
it do
sign_in!("admin@trb.org", "123456")
visit "/?as=jimmy@trb.org"
page.must_have_content "Hi, jimmy@trb.org"
end
总结
通过以上步骤,我们实现了授权和多态性,确保了应用系统的安全性和灵活性。通过测试,我们验证了这些功能的正确性,并且可以方便地进行扩展和维护。在开发过程中,我们可以利用 Trailblazer 的架构特点,如继承和组合,来实现代码的复用和可维护性。同时,通过测试副作用而不是内部实现,我们可以更轻松地进行重构。
整个流程可以用以下 mermaid 流程图表示:
graph LR
A[表单授权显示] --> B[更新操作测试]
B --> C[测试副作用]
C --> D[编辑表单渲染测试]
D --> E[删除功能实现]
E --> F[删除操作测试]
F --> G[模拟用户功能]
G --> H[导航栏显示模拟用户信息]
H --> I[模拟用户功能测试]
通过这种方式,我们构建了一个安全、灵活且易于维护的应用系统。
授权与多态性:构建安全且灵活的应用系统
10. 操作与授权的深入分析
在实现授权和多态性的过程中,操作和授权的关系至关重要。以
Thing::Delete
操作为例,它的核心逻辑是删除事物及其相关资源,但在执行之前需要经过授权检查。具体来说,操作的授权逻辑由
Thing::Policy
类控制,通过
delete?
规则来判断用户是否有权限进行删除操作。
这种设计使得操作和授权分离,提高了代码的可维护性和可扩展性。例如,如果需要修改删除操作的授权规则,只需要修改
Thing::Policy
类中的
delete?
方法,而不需要修改
Thing::Delete
操作的核心逻辑。
操作和授权的关系可以用以下表格总结:
| 操作类 | 授权规则 | 说明 |
| — | — | — |
|
Thing::Delete
|
Thing::Policy#delete?
| 判断用户是否有权限删除事物 |
|
Thing::Update
| 未详细提及,但类似 | 判断用户是否有权限更新事物 |
11. 模拟用户功能的优势与应用场景
模拟用户功能在开发和测试过程中具有重要的优势。它允许开发者以不同用户角色的视角来浏览应用,从而更好地测试和验证不同用户权限下的功能。例如,在测试删除功能时,管理员可以模拟普通用户或作者的身份,检查不同角色的删除权限是否正确。
模拟用户功能的应用场景包括:
-
测试不同用户角色的功能
:确保不同用户角色在应用中的操作权限和显示内容符合预期。
-
调试权限问题
:当出现权限问题时,通过模拟用户功能可以快速定位问题所在。
-
演示和展示
:在演示应用时,可以模拟不同用户角色来展示应用的多态性和灵活性。
12. 代码复用与可维护性
在整个实现过程中,我们充分利用了 Trailblazer 的架构特点,如继承和组合,来实现代码的复用和可维护性。例如,在
Thing::Delete
操作中,我们继承了
Trailblazer::Operation
类,并包含了
CRUD
模块,这样可以复用一些通用的操作逻辑。
同时,通过将授权逻辑封装在
Thing::Policy
类中,不同的操作可以共享相同的授权规则,避免了代码的冗余。这种设计使得代码更加清晰,易于理解和维护。
代码复用和可维护性的实现方式可以总结如下:
-
继承
:通过继承父类来复用通用的操作逻辑。
-
组合
:通过包含模块来复用特定的功能。
-
封装
:将授权逻辑封装在独立的类中,便于共享和修改。
13. 测试的重要性与策略
测试是确保应用系统正确性和稳定性的关键环节。在本文中,我们通过编写各种测试用例,如更新操作测试、删除操作测试和模拟用户功能测试,来验证应用的功能是否符合预期。
测试的策略包括:
-
测试副作用
:通过测试操作的副作用来验证操作的正确性,而不是直接测试内部实现。例如,在测试
Thing::Delete
操作时,通过检查事物是否被删除以及相关图片文件是否被移除来验证操作的正确性。
-
覆盖不同用户角色
:针对不同的用户角色编写测试用例,确保不同用户权限下的功能都能正常工作。
-
模拟不同场景
:在测试中模拟各种可能的场景,如匿名用户、已登录用户和管理员,以验证应用的多态性和灵活性。
测试的重要性和策略可以用以下 mermaid 流程图表示:
graph LR
A[编写测试用例] --> B[测试副作用]
B --> C[覆盖不同用户角色]
C --> D[模拟不同场景]
D --> E[验证功能正确性]
14. 未来扩展与优化方向
基于现有的实现,我们可以考虑以下未来扩展和优化方向:
-
增加更多操作类型
:除了更新和删除操作,还可以实现创建、查询等操作,并为每个操作添加相应的授权规则。
-
优化授权规则
:根据实际业务需求,进一步细化和优化授权规则,例如根据事物的状态、用户的等级等因素来判断用户的权限。
-
集成第三方认证服务
:可以考虑集成第三方认证服务,如 OAuth、LDAP 等,以提高应用的安全性和用户体验。
-
性能优化
:对操作和授权逻辑进行性能优化,减少不必要的计算和数据库查询,提高应用的响应速度。
总结
通过实现授权和多态性,我们构建了一个安全、灵活且易于维护的应用系统。在开发过程中,我们充分利用了 Trailblazer 的架构特点,实现了代码的复用和可维护性。同时,通过编写全面的测试用例,我们验证了应用的功能正确性和稳定性。
未来,我们可以根据实际业务需求,进一步扩展和优化应用,提高应用的性能和用户体验。整个开发过程遵循了良好的设计原则,为构建高质量的应用系统提供了有益的参考。
整个开发过程的关键步骤可以总结如下:
1. 实现表单授权显示,根据用户权限显示特定表单字段。
2. 编写更新操作测试,验证不同用户角色的更新权限。
3. 测试操作的副作用,确保操作的正确性。
4. 进行编辑表单渲染测试,检查不同用户角色下的表单显示。
5. 实现删除功能,包括事物删除和相关资源清理。
6. 编写删除操作测试,验证不同用户角色的删除权限。
7. 实现模拟用户功能,方便测试和开发。
8. 在导航栏显示模拟用户信息,提高用户体验。
9. 编写模拟用户功能测试,确保功能正常工作。
10. 深入分析操作与授权的关系,提高代码可维护性。
11. 探讨模拟用户功能的优势和应用场景。
12. 总结代码复用和可维护性的实现方式。
13. 强调测试的重要性和策略。
14. 提出未来扩展和优化方向。
通过以上步骤,我们构建了一个完整的应用系统,实现了授权和多态性的完美结合。
超级会员免费看
172万+

被折叠的 条评论
为什么被折叠?



