Calendar

十二月 2009
« 十一月   一月 »
 123456
78910111213
14151617181920
21222324252627
28293031  

Categories

Redmine 的 Repository Controls plugin(續)

在正式把手邊的專案移上 redmine + svn 之前,
我還是花了點時間把這個 plugin 做改善,
資料庫也從 sqlite 換到了 mysql 上,
也把 redmine 從 ports 的版本直接換成官方 svn trunk 裡的版本。

換上 svn trunk 的版本後我馬上發現到一件事,
就是新版的 redmine 有所謂的 multi-role 機制,
舉例來說一個 project 的 member 可以同時是 Manager 和 Developer,
這讓我想通了為什麼這個 plugin 的作者會以 role 的 position 來做優先順序的判斷,
舉例來說 Manager 的 position 是 3 而 Developer 的 position 是 4,
那麼檔案庫存取權限就是看 Manager 的為準;
雖然我也思考過是否有更聰明更複雜的判斷方法,
不過還是以單一 role 來判斷最不容易導致混亂,
也是唯一最貼近 svn 原本 authz 運作模式的實現方法,
更何況這是每 checkout 一個檔案就要跑一次的動作,
SQL 的查詢次數和複雜度也不便設計得太誇張,
不然光是 checkout 和 update 就可以慢到會等死人的程度。

不過利用 mod_perl2 掛 access/auth/authz 的 handler 有一個問題,
那就是跑到 auth 這個 step 的時候必須先發送一個 AUTH_REQUIRED 給 browser (或 svn client),
這樣 browser 才會跳出輸入帳號密碼的 dialog (svn client 也才會要求輸入密碼),
加上 HTTP 協定並不是 keep alive 的東西,
我很難去判斷這是第幾次發送 AUTH_REQUIRED 目前這個 client,
所以如果 user 希望用帳號密碼完全空白的 anonymous 模式存取檔案庫,
那就只會不斷的重複被詢問帳號密碼而不是被直接放行;
關於這點我最後的解決方式就是把存取的 URL 拆成 http:// 和 https:// 兩種協定,
perl module 也是一分為二,
走 http:// 的就是完全不需要打帳號密碼一律視為 anonymous 的存取,
走 https:// 的就視為一定要輸入帳號密碼的 members,
這樣做想想其實也還蠻合理的,
畢竟吃 LDAP 帳號密碼的這種架構下允許 members 走 http:// 實在太恐怖。

下面是分離出來給 anonymous 存取用的 perl module:

下面這個是給 members 登入後才能存取用的 perl module:

註解方面我就沒有時間去慢慢改慢慢加了,
反正目前稍微測試了一下是可以在 virtual host 的 / 為起點的設定下正常運作,
而原作者的設計是假設檔案庫一定不是在 / 下面而是在某層目錄之下,
這個部分我也保留了原作者用 regular expression 的處理方式,
所以如果不是掛在 / 的話能不能正常運作我也不知道。


如果你的 svn 位址不是在 / 開始 (如 http://svn.xxx.xxx/repos 而不是 http://svn.xxx.xxx/),
請自行修改 get_project_identifier() 及 get_requested_path() 中 if($location ne "/") { ... } 的部分。
可以仿照 else { ... } 內的邏輯實作一份。
因為我的檔案庫掛在根目錄,
所以對於不是掛在根目錄的 case 我是直接使用原作者有問題的實作。

NOTE: (for users who don't understand Tranditional Chinese)
If your SVN repositories aren't located on the root directory of your website, you MUST re-implement some codes in get_project_identifier() and get_requested_path().

Since my SVN repositories are located on the root directory, I can only implement and test this case.
I know that I can generalize these codes in order to handle all cases, but I don't have enough time and environments.
Thus, I only publish my modifications in my blog rather than Redmine's forum.

至於那個 SQL 的寫法,
就我讀過的書來說如果寫 SELECT xxx FROM a, b, c 是所謂的 CROSS JOIN,
而且我記得 CROSS JOIN 的 performace 會比較差 (但實際測試似乎都會被 MySQL 的 optimizer 處理到沒差多少),
所以我還是直接寫成 SELECT xxx FROM a INNER JOIN b INNER JOIN c 的形式,
原作者用的是前者的形式而且條件式的部分一律使用 WHERE 不使用 ON (那種寫法確實也沒地方能擺 ON),
我並不是 SQL 的專家所以也沒有在研究這個的,
不過之前讀過一本 O'Reilly 的 SQL 之美學似乎作者是比較支持前者的寫法,
到底哪種好哪種不好可能就要問資管畢業的夭壽仔了 (他好像跑去當兵都找不到人),
我是覺得可讀性來說用 INNER JOIN + ON 的方式可以一層一層看出設計者的目的,
會比較容易理解就是了;
原作者寫的 SQL 我是沒有動它,
所以 code 裡會同時看到兩種寫法。

另外還發現一個小 bug,
就是如果編輯已經設定好的 role 權限,
然後把 read / write 的權限通通都取消的話,
儲存以後會發現權限會保持原狀而不會變成 None (新增的時候兩個都不勾倒是會正常),
這個部分要修改一下 controller 裡的 edit method:


2009-12-23 Update
根據機八林餅幹的說法,
SELECT xxx FROM a, b, c 這種寫法 MySQL 會做 cache,
單次跑出來是數據會差不多,
但是跑多幾次就會發現這種寫法會比較快。

2010-01-27 Update
今天發現就算用這個改寫過的 RedmineRepoControl.pm,
在 MPM=worker 的模式下用 svn copy 還是會爆炸。
gdb 也攔不到原因只會看到直接 program exited with code 02 跳掉,
所以 svn 會說 connection truncated。
目前懶得追了,
只確定是某次 DBI->connect 呼叫下去就爛掉。
跟以前遇到的狀況一樣只要把 apache 改回 MPM=prefork 就會正常。
Redmine 官方的 Redmine.pm 也有遇到一樣的問題:
http://www.redmine.org/boards/2/topics/7593
最新的一篇是 4 個月前了,
大概是沒救了。

  • 你好!
    我最近也在用这个Redmine,而且很需要这个Repository Controls plugin 这个的功能.
    根据这个插件的官方文档进行了安装.确保在使用redmine.pm的情况下可以通过项目的关联进行正常的SVN存取.但是只要改成RedmineRepoControl 后就不行了.
    我的apache配置文件是:

    使用/svn无法进行CO 提示一次用户名之后直接401错误,如果不是用这个插件 正确的提示会在root密码回车之后再提示输入新的用户名.
    linux-ajian:/tmp # svn co http://222.111.56.199/svn/aa aa
    认证领域: redmine
    “root”的密码:
    svn: 服务器发送了意外的返回值(401 Authorization Required),在响应 “OPTIONS” 的请求 “http://222.111.56.199/svn/aa” 中
    使用/svn-private进行CO 可以进行匿名的CO 但是无法存入
    # svn ci -m "aaa"
    svn: Commit failed (details follow):
    svn: MKACTIVITY of '/svn-private/aa/!svn/act/0dc0ff55-6c78-45cd-975f-e34840245075': 403 Forbidden (http://127.0.0.1)

    我也看过你的文章 并且把你的PM文件也都添加上去了 但还是不行.这个插件的资料比较少,希望能帮我分析下问题的原因是什么.谢谢.

    • 官方的 Redmine.pm 會先判斷你 redmine 裡那個 project 是不是設定成 public,
      如果是 public 而且你的存取動作是讀取性質的,
      就會直接取消後續的驗證動作直接讓你讀取。
      如果是寫入性質的,
      就會發個 401 要求你輸入帳號密碼。
      client 收到 401 會跳出對話框或提示符號要求使用者輸入帳號密碼,
      如果持續得到 401 才會放棄要求 user 輸入並回報 401 給 user。
      Repository Controls Plugin 是改自官方的 Redmine.pm,
      所以這條規則相同。
      但我修改後的版本就完全不管 project 是否被設定成 public,
      而是完全按照各路徑的權限設定來判定。

      遇到 403 的可能性有兩種。

      一種是你沒在 <Location> 標籤裡放入 Allow from all,
      或是你寫了 Allow from 但是你的連線來源卻不是那個 IP。

      另一種就是你裝了 Repository Controls Plugin 並在 apache 的設定裡使用它。
      這個 plugin 跟官方那個的差別是官方是只要登入就可以寫入任意路徑,
      而這個 plugin 則是還會確認你是否有某個路徑的讀取/寫入權限。
      這 plugin 剛安裝好的時候權限設定的部分是空白的,
      你至少必須去 project 的設定頁面替 Manager 這個 role 開啟 / 的 read/write 權限才行:

      否則你就算是用有 Manager 權限的身份登入也是照樣收到 403。
      我是忘了這 plugin 的原作者是不是這樣設計的,
      不過如果你用我改過的版本那就一定是這樣。

  • 上面配置文件中的< 尖括号都被过滤掉了, 需要用什么标识才可以保存原始代码呢?

    • 可以試試看 <pre lang=""><> 或 <code> 標籤。
      不過我也不確定 wordpress 給不給在 comments 裡面用,
      因為 web UI 上沒看到相關設定,
      似乎需要直接改 code 或寫 plugin 去 hook 它。

  • 补充,我使用的redmine是 trunk的
    另外发现该插件官网的配置文件中
    PerlLoadModule Apache::RedmineRepoControl 这个是没有Authn的
    但后面的
    PerlAccessHandler Apache::Authn::RedmineRepoControl::access_handler
    PerlAuthenHandler Apache::Authn::RedmineRepoControl::authen_handler
    PerlAuthzHandler Apache::Authn::RedmineRepoControl::authz_handler
    又是存在的 不知道这个官网文档是否正确?

    • 照 plugin 官方的設定方式是沒有問題的。
      如果是用我的那兩個 .pm 來設定的話,
      anonymous 用的 vhost 是這樣設:

      需要認證的 vhost 則是:

  • 我检查了日志不能够checkout的原因是
    Use of uninitialized value in pattern match (m//) at /usr/lib/perl5/site_perl/Apache/RedmineRepoControl.pm line 244.\n
    在redmine上操作该插件出现的错误有
    NoMethodError (undefined method each' for nil:NilClass):
    vendor/plugins/redmine_repository_control/app/controllers/repository_controls_controller.rb:15:in
    new'
    但解决办法还没有找到 perl语言不是很熟

    • 下面那個不知道。
      上面那個是路徑的問題,
      因為那個 plugin 的 get_project_identifier 和 get_requested_path 這兩個 function 沒有寫得很好,
      可能會造成 project 的 id 或是 path 變成 null;
      你可以在 .pm 檔裡插入一些 print,
      然後用 httpd -X 啟動 apache 來看它的輸出做一些基本的 debug。

      之前印象中原作者的寫法有強烈要求路徑名稱要完整,
      也就是不能用 http://222.111.56.199/svn/aa,
      而是必須用 http://222.111.56.199/svn/aa/ 這樣結尾有 / (目錄才需要),
      不然是會發生錯誤的。

      我修改後的 .pm 檔也有保留原作者的部分:

      其實要改到完美不保留原作者寫的部分也不是不行,
      只是當初實在懶得做下去了。

  • 现在最大的问题就是 是路徑的問題,
    "因為那個 plugin 的 get_project_identifier 和 get_requested_path 這兩個 function 沒有寫得很好,
    可能會造成 project 的 id 或是 path 變成 null;"
    确认我做过程序方面调试了
    ($path) = $r->uri =~ m{$location/*[^/]+(/.*)};
    这个部分$path得到的是空的 $r->uri 得到的是/svn/aa $location得到的是 /svn 但是经过=~运算后就成了空了 我在我本地用perl引入这两个变量执行这个语句也是为空. 不知道这取值要取到什么才是正确的 这句的意义在哪?

    另外你说的checkout的格式我也添加了/ 还是一样的.
    我查看了你贴的地个图 跟我的那个有些差别 是否可以大概的描述下你的安装过程和版本情况.
    你自己改的这两个脚本anonymous 这个是任何人都可以访问吗? 是80的端口的 但是需要认证的是443的 我用的是官网的做法 如果是要可以直接访问的是用/svn-private做了IP的限制 而/svn就是需要用户认证的.

    • m{regular expression} 或 m/regular expression/ 這類 m 後面跟著各種成對標點括起的,
      表示要用裡面的 regular expression 做 match 的動作,
      包住 regular expression 的只要是成對的標點大都可以,
      這個 plugin 用 {} 主要是可以避免內部出現 / 號被當成 expression 的結尾 (因為是分析 URL 字串內容易出現 / 號)。
      而 regular expression 中出現圓括弧表示要記憶 match 到的 string。
      =~ 的左邊你可以把它當成 regular expression 要 match 的 input。

      如果單純寫 if($str =~ m{regexp}) 則代表判斷是否 match 成功,
      如果寫 ($var1, $var2) = $str =~ m{(subexp1)(subexp2)} 則代表 $var1 要儲存 subexp1 所 match 到的 string,
      而 $var2 代表要儲存 subexp2 所 match 到的 string。
      要儲存的個數取決於你 regular expression 出現成對圓括弧的次數。

      [^/]+ 表示「一個或多個」非 / 號的 string,
      (/.*) 則表示要記憶由 / 號開始後面接著零到多個任意字元的 string。
      所以假設你 $location 是 "/svn" (這取決於你 apache 的設定),
      輸入的網址是 http://host/svn/aaa/bbb.txt
      $r->uri 會是 "/svn/aaa/bbb.txt",
      m{$location/*[^/]+(/.*)} 的 [^/]+ 會 match 你的 aaa,
      (/.*) 會 match 你的 /bbb.txt 並儲存。
      既然寫的是 ($path) = $r->uri =~ m{$location/*[^/]+(/.*)},
      那麼 (/.*) 的 match 結果就會被儲存到 $path 中,
      所以 $path 就是 "/bbb.txt" 了。

      如果你輸入的是 http://host/svn/aaa/
      (/.*) 會得到 "/" 這唯一一個字元,
      也就是根目錄,
      這樣還不至於變成空值;
      但是如果你輸入的是 http://host/svn/aaa
      那 (/.*) 當然就沒辦法 match 了。
      當然一般的檔案庫下都還會有 trunk、branches、tags 這三個目錄在,
      所以正常來說也很少遇到會讓 $path 變成空的狀況。

      這個問題其實去年 10 月就有人 feedback 給 plugin 的原作者過:
      http://code.google.com/p/redminerepositorycontrol/issues/detail?id=1
      而上個月中原作者也說他做了一些 fix,
      你如果跟我一樣是在那之前就開始用的,
      可以 svn update 一下看看原作者是否修正了這個問題。

      我改的 plugin 如果 anonymous 有打開讀取權限,
      任何人就能從不用認證的 port 80 直接 checkout 檔案,
      但不影響 port 443 這個需要認證的 port 的行為。

      用哪套 plugin 是取決於你的需求,
      Redmine 官方那個是「只要登入就能任意寫入」,
      也就是登入成功就有完整的存取權。
      Repository Controls Plugin 以及我改過的版本,
      是在模擬 svn 檔案庫的 conf/authz 這個檔案在做的行為,
      會進一步確認你登入的 id 是否有某個 path 的 read/write 權限。
      如果你的需求沒有要設到這麼細,
      使用官方的那個 Redmine.pm 就可以了。
      要是你沒有堅持一定要在 web UI 上設定每個 id / roles 存取權限,
      你也可以手動編輯各檔案庫的 conf/authz 檔來達成目的,
      當然 apache 上也要有對應的設定指向該檔案就是了。

      另外官方建議的 /svn-private 這個設定主要是讓 Redmine 的 Repository 這頁能直接讀取你的檔案庫,
      它基本上只適合用在 read-only 性質,
      如果你要 commit 的話我不建議你走這條路線,
      因為可能會無法記錄是誰 commit 的。

  • 我现在做了些程序的修改

    并且修改

    可以实现按用户进行checkout了.
    但其实还是有错误 日志中
    DBD::mysql::st execute failed: Unknown column 'members.role_id' in 'where clause' at /usr/lib/perl5/site_perl/Apache/RedmineRepoControl.pm line 282.
    DBD::mysql::st fetchrow_array failed: fetch() without execute() at /usr/lib/perl5/site_perl/Apache/RedmineRepoControl.pm line 283.
    并且不能进行提交 也是报同样的错误 我确认在插件上我已经添加了/的读写权限.
    从上面的错误可以看出来 是不存在相应的列 难道这个版本不相符?我redmine用的是trunk的 插件也是checkout最新的.
    是否有gtalk类似的联系方式 这样交流太慢了.

    • 我最近比較常睡白天所以就算有那種通訊軟體也沒意義,
      因為最近比較忙。

      你現在說我才發現我的版本也有一個地方沒改到,
      不過神奇的地方是目前為止都沒有跳出過錯誤訊息來。

      基本上 members.role_id 這個 column 在 trunk 已經拿掉很長一段時間了,
      trunk 的版本多了一張叫 member_roles 的 table (就是在做正規化遇到 many-to-many 關係時要做的 junction table),
      你要把它一起 join 進來才能拿到 role_id。
      其實官方的 Redmine.pm 已經修正這個問題快一年了:
      http://www.redmine.org/issues/3330

      RedmineRepoControl.pm 其實是從官方舊版的 Redmine.pm 改來用,
      所以也是會有這個問題,
      原作者大概沒有跟上 trunk 的版本。

      說起來我也差不多該睡了...

  • 我的gtalk就是我的邮箱 msn是5root@live.cn 如果可以直接交流会更快些 谢谢.

  • 我已经可以正常使用了.而且没有任何错误了.已经可以满足了我的需求了.就是后台有点傻,但还能用.
    解决方法其实很简单,就是使用的你的RedmineRepoControl.pm 但你的之前使用的时候就会出现$path为空的情况 我想这个可能和perl的版本有关系吧.我的perl是5.8.8 取到的就对的但就如你上说的 至少也应该得到/ 这个版本的就是得到的空 我也没有去深究.
    解决了上面为空的问题再解决另外一个问题后发现这个插件的数据库查询语句有问题原来用的是老的文件改的.本打算就把这个文件通看一遍看要取什么数据库自己来写的,突然想到你也用得正好可能你的数据库查询是正确的所以就把你的那个文件改了楼上那个问题就可以正常使用了.
    要谢谢你为我细心的解答问题.

    • 其實我自己是開一個 svn.xxx.org 的 virtual host,
      所以是寫 <Location /> ... </Location>。
      並不會使用到我保留的原作者所寫的那前面三行,
      而是後面 else { } 裡我自己寫的部分:

      我用 perl 的 split 根據 "/" 這個字元把 string 分解成 array,
      然後再依據 array 的 size 以及內容做一些判斷;
      像是 http(s)://host/a/b/c 的話我的 @path_items 會是一個 { 'a', 'b', 'c' } 的 array。
      這個部分的 code 沒有辦法直接套用在 <Locaation /svn> 這種設定上,
      因為還必須多寫一些 if 去判斷 $location 跟 $path_items[0] 去設定一個 base_index 值,
      再把後面的 $path_items[1] 之類的動作改成 $path_items[base_index + 1] 這樣讓它聰明一點。

      但是就像前面說的其實弄到會動之後就懶得做一般化了,
      所以不是用 <Location /> 的就丟給原作者的 code 負責,
      這樣原作者有更新那一行 code 的時候你們也能直接貼上去當 patch。