modeling photos/albums (2)
承上篇,加了不少新東西,不過還不是很完備,還需要再調整。
其中 photos_for_visitor 已經拉到 photos/albums 層級,
也就是說 Photo 會有 albums_for_visitor, Album 會有 photos_for_visitor.
兩者都是很單純地改寫成:resources.for_visitor(visitor, user)
但單單這樣取資料是不夠的,因為顯示頁面也需要存取限制。
像是如果閱讀一個 private album, 然後進去啥鬼都看不到,
實在沒什麼意義,這一頁本身就應該吐出 403 Forbidden.
我理想上是希望能做到類似這樣:
if @visitor.grant(album)
@photos = album.photos
else
render_optional_error_file 403
end
不過真的要做到這樣實在不容易,現在沒那麼多時間讓我慢慢試。
實際上目前我只做到:
@visitor.grant(resource, mode, callbacks.reverse_merge(:failed => lambda{
if @visitor.id == User::GuestID
CAS::Filter.filter self
else
render_optional_error_file 403
end
}))
也就是說,透過一個 block 做 abstraction, 而不在內部維護一個 granted 的 state.
上面針對 failed 的動作是檢查這個沒權限的人登入沒?有登入的話,就是 403.
沒登入的話,請登入看看權限夠不夠...
把這個再拿去包一層,所以真正的 controller 寫的大概是:
visitor_open(Album.find(params[:id]), :write){ |album|
@album = album
@photos = album.photos.paginate :page => params[:page]
}
這邊是用 block, 因為我的包法是如果有 block_given? 則把 block 給 granted.
另一個寫法則是指定 :granted => lambda{...} 和 :failed => lambda{...}.
需要注意的是,這個丟進來的 album 其實不是真正的 album, 而是一個 delegate.
(用 BasicObject in 1.9, ActiveSupport::BasicObject in 1.8)
我命名為 User::ResourcePerformer, 會針對所有操作做權限檢查,
還有改寫 methods call. 像是上面寫 album.photos 實際上不是呼叫 photos,
見下:
elsif resource.class.reflections[msg].ergo.macro == :has_many &&
msg.to_s.classify.constantize.reflections[:permission]
resource.__send__("#{msg}_for_visitor", performer, *args, &block)
如果呼叫的對像有 has_many 的關係,且存在 permission 的任何 association,
則改寫為 "#{msg}_for_visitor", 並把 visitor 丟進去。
所以呼叫 album.photos 實際上會變成 albums.photos_for_visitor(@visitor, ...)
除此之外,諸如 assignment, save, destroy, update_attributes 等也有檢查
write 權限。這是避免你用 visitor_open(album, :read) 然後卻去呼叫 save.
visitor_open 只是檢查會不會進入 block, 而不是其操作是否正確。
(不過其實如果用 :read 去開,我會呼叫 readonly! 所以可能也不用擔心)
當然這有缺陷,所以才會希望寫成真正的 granted 機制,不過不好寫...
所以就先用這種多重檢查的方式做看看。
檢查的方式也很簡單,只是呼叫 photo/album 的 readable_for? 和 writable_for?
至於那兩個怎麼寫,應該不用講了,很簡單,上一篇也有提到。
*
也就是說,現在的作法完全就是 photo/album 本身沒有權限檢查,
但是透過 visitor_open 就有。所以記得在 controller 裡面,
一定要用 visitor_open, 而不要直接操作 photo/album.
有了以上的東西,操作就可以變得很複雜。像是移動 photo 時,
先 visitor_open album, 在裡面:
massively_update_photos(params[:photo]){ |photo|
photo.move_out(album) if photo.to_move_out == '1'
}
這個 to_move_out 是一個 dummy value, 僅存在記憶體中,
在 update_attributes 時會自動寫入。
而 massively_update_photos 裡面,也要一張張 visitor_open,
以確保每一張照片使用者都有寫入權限。(不然自己 post 給 server 就能亂移動)
photos_hash.each{ |photo_id, attributes|
visitor_open(Photo.find_by_id(photo_id), :write){ |photo|
photo.attributes = attributes
photo.tag_list = TagList.from attributes[:tag_list]
# user should add tag list from all photos as well.
@visitor.tag_list.add attributes[:tag_list], :parse => true
yield(photo) if block_given?
photo.save
}
}
此外,visitor_open 會忽略 nil, 所以不用擔心 find_by_id 的結果會是 nil.
權限不足的就跳過即可,更新權限足夠的,要有一定的容錯能力。
in_place_editing 則也需要修改,加上可傳入的 predicate 判斷 writable_for?
至此,整個權限管控差不多就告一段落了....... 才怪 orz
password 忘記加上去了... 這邊應該要從原本 render_optional_error_file 著手,
在 session 中記住使用者曾經輸入過的 password, 每次重新判斷 password 是否正確。
如果這個架構寫得夠好,我相信幾小時內應該可以掛上 password 吧... 希望如此。
至於再加上 role 的機制,只好繼續往後延了。
寫程式之所以有趣,在於會不斷發現事情其實沒那麼單純(誤)
0 retries:
Post a Comment
Note: Only a member of this blog may post a comment.