miauのブログ

はてなダイアリー「miauの避難所」をはてなブログに移行しました

ProtectedMacro で !protected 部分の表示に WIKI_MODIFY を必須にするパッチ

今のプロジェクトで使っている Trac は、anonymous にすべての VIEW 権限を与えています。社内のプロジェクト間でノウハウを共有できるようにこのような形にしているのですが、サーバのログインパスワード等はプロジェクト外の人には知られたくありません。

このような場合に使えそうなマクロを探してみると、ProtectedMacroAccessMacro というものが見つかりました。それぞれ Wiki の一部の領域に閲覧制限をかけることができるプラグインですが、

  • ProtectedMacro のほうは unauthorized user 向きのメッセージが設定できる
  • AccessMacro のほうはパーミッションをより詳細に設定できるが、今回ここまでの機能は必要ない

ということで、ProtectedMacro のほうを試してみました。

インストール

普通にインストールできます。一応さらっと手順を書いておくと、

  1. .zip をダウンロードして解凍
  2. python setup.py bdist_egg
  3. su easy_install dist/*
  4. Apache 再起動

という感じです。

権限については

  • PROTECTED_VIEW
  • PROTECTED_RED_VIEW
  • PROTECTED_BLUE_VIEW
  • PROTECTED_GREEN_VIEW

の 4 つがあって、それぞれ

  • !protected
  • !protected-red
  • !protected-blue
  • !protected-green

となっているフィールドを見る/添付ファイルを見るための権限になっています。TRAC_ADMIN であれば全部見ることができるようです。

試してみる

Wiki 中に

{{{                                                
#!protected
#:ログインユーザのみ閲覧できます
 * id: ******
 * pass: ******
}}}

>
と書いておくと、

  • PROTECTED_VIEW 権限がない場合

  • PROTECTED_VIEW 権限がある場合

のように表示される、と。いい感じですね。

問題点

閲覧する場合は上記のようにうまく機能するのですが、編集画面では PROTECTED_VIEW 権限がなくても !protected な領域が見えてしまいます。今回は PROTECTED_VIEW 権限がないユーザ=WIKI_MODIFY 権限もないユーザなので問題なりませんが、プロジェクトによっては問題になるかもしれません。

上記はそういう仕様ということでいいんですが、もう一つ問題がありまして。Wiki の履歴から diff を見ると、PROTECTED_VIEW 権限がなくても !protected な領域が見えてしまいます。diff を見るための権限(WIKI_DIFF みたいな)が別にあってそこで制御できればいいんですが、diff に必要な権限は WIKI_VIEW なので、ここでは制御できません。

バグ報告されてたりパッチがあがってたりしないかな?と見てみると、

さらに「異なるフォーマットでダウンロード」でテキストをダウンロードした場合も同様の問題がある(こちらも権限では制御できない)とのこと。そしてパッチはあがっていない、と。

まるっきり仕様が破綻している感じなので、とりあえずさっきのチケットに「diff でも見えちゃうよ」とコメントを追加して ProtectedMacro は忘れることに。

AccessMacro を試す?

で、もうひとつのプラグイン AccessMacro のほうを試してみようとして。「まさかこっちにも同じ問題があるのでは・・・」と先にチケットを見てみると・・・ありました。

こちらのチケットにはパッチはあがっていて・・・同じ方法で ProtectedMacro も対応できそうな気がします。

ただこのパッチは Trac 本体に手を入れるやり方のようで・・・今の Trac は私が管理者じゃないので、それは避けたいです。で、プラグイン側でうまいことできないかなー?と調べてみると、なんとかなりそうだったので ProtectedMacro のパッチを作ってみました。

パッチの内容

WIKI_MODIFY 権限がないと diff やテキストのダウンロードが行えなくするものです。diff や wiki 記述が読めないのは少し痛いのですが、パスワード等の情報がプロジェクト外に漏れる or プロジェクトで共有されていないリスクに比べればマシかなと。

処理としては、trac/wiki/web_ui.py に AccessMacro のチケットにあがっていたパッチを当てて、必要な処理/分岐だけ抜き出して、IRequestFilter の pre_process_request で処理してあるだけです。マクロでもないのに macro.py に入れちゃってますが、まあ IPermissionPolicy が元々入っていたし・・・問題ないんじゃないかなと。

Index: macro.py
===================================================================
--- macro.py	(revision 6673)
+++ macro.py	(working copy)
@@ -2,8 +2,10 @@
 from trac.attachment import Attachment
 from trac.core import *
 from trac.perm import IPermissionRequestor, IPermissionPolicy
+from trac.web.api import IRequestFilter
 from trac.wiki.api import IWikiMacroProvider
 from trac.wiki.formatter import format_to_html
+from trac.wiki.model import WikiPage
 
 LEVELS = {"protected":{"action":"PROTECTED_VIEW", "style":"border-left:2px solid red; padding-left:3px"},
           "protected-red":{"action":"PROTECTED_RED_VIEW", "style":"border-left:2px solid red; padding-left:3px"},
@@ -100,4 +102,40 @@
     # IPermissionRequestor,
     def get_permission_actions(self):
         return [level["action"] for level in LEVELS.values()]
-    
+
+class ProtectedFilter(Component):
+    implements(IRequestFilter)
+
+    # IRequestFilter
+    def pre_process_request(self, req, handler):
+        action = req.args.get('action', 'view')
+        pagename = req.args.get('page', 'WikiStart')
+
+        if pagename.endswith('/'):
+            req.redirect(req.href.wiki(pagename.strip('/')))
+
+        page = WikiPage(self.env, pagename)
+
+        if req.method == 'POST':
+            pass
+        elif action == 'delete':
+            pass
+        elif action == 'edit':
+            pass
+        elif action == 'diff':
+            if page.exists:
+                req.perm(page.resource).require('WIKI_MODIFY')
+        else:
+            format = req.args.get('format')
+            if format:
+                req.perm(page.resource).require('WIKI_MODIFY')
+
+        return handler
+
+    # IRequestFilter
+    def post_process_request(self, req, template, content_type):
+	return (template, content_type)
+
+    # IRequestFilter
+    def post_process_request(self, req, template, data, content_type):
+	return (template, data, content_type)

パッチは さきほどのチケット にも添付しているので、本当はこちらを使っていただいたほうがいいんですが・・・チケットに添付したほうはデバッグコード消すの忘れていたので、パッチを当てる方はその部分を消してから使ってください。たまにやっちゃうけど、こういうのは恥ずかしいなぁ。

あと、添付ファイルのほうは今回試していませんので、動作に問題ないことを確認したうえでご利用ください。

(2009-10-13 追記)

検索したときも plain text が見えちゃいますね・・・。ひとまず SEARCH_VIEW はずして対応しています。

(2010-08-13 追記)

2010-02-17 くらいにパッチ適用されてたみたいです。公式の最新版を使っていただく形で問題ないかと。