Redmineを読み取り専用にしたいけど、どうしよう？


## はじめに

この記事は、[Unofficial Redmine Cooking](https://redmine.tokyo/projects/unofficialcooking) の2016年度版[アドベントカレンダー用記事](https://redmine.tokyo/projects/unofficialcooking/issues/calendar)も兼ねております。
[Unofficial Redmine Cooking](https://redmine.tokyo/projects/unofficialcooking) のサイトについては、末尾にご案内します。合わせてご興味のある方はご覧いただけると幸いです！

脈絡ないですが、いつもRedmine作業ふくめRubyMineにがお世話になっていますので、「るびまい子さん」（勝手キャラ）を添えてみました...。

![るびまい子さん.png](https://qiita-image-store.s3.amazonaws.com/0/27976/8592bce6-a4cd-7746-566f-8f662091ab0a.png "るびまい子さん.png")

それでは、以下、内容です。


## やりたいこと

**やりたいこと：**

- Redmineの参照は許可するけれど、更新は全て止めたい

**理由 / 背景：**

- サーバの移転・移行により新サーバにデータを同期させる間、データの整合性を担保するため更新を防ぎたい
- ただし、チケットやWikiは参照させたいため、Sorryページへの切り替えやメンテナンスプラグインの利用でない方法をとりたい

予算や潤沢なリソース（マシン）があって、切り替えが瞬断〜1時間くらいで済むのなら、このような作業は不要かと思います。
以前管理者をしていた際には、そこまでの環境は準備できなかったため、Redmineのメジャーバージョンアップの際は一ヶ月くらい前から日程を決めてアナウンスをしておき、準備を重ねた上でメンテナンス・バージョンアップを行なっていました。
方法自体はよくある新しいサーバに環境を構築しておき、ある時点からのデータを新環境側に同期させた上でDNS切り替え、というものでした。

## Redmine以外での方法など

一番の目的は「書き込みを阻止したい」です。必ずしも、技術的な対応でなくとも、ユーザさんに周知の上で運用で更新を回避することができれば、それが良いのかなと思います。

ただ、どうしてもAPI経由でプログラムなどで更新がリクエストされてしまうことはあります。
そのあたり厳密に対応するために、当時考えていたのはこのようなことでした。（今はもっと楽で良い方法がいろいろあると思います！）

- DBでの対応が可能なら、読み取り専用モードにする
- 更新系のリクエスト (CRUD) を受け付けないようにフロント、Webサーバ側の設定を調整する

ただし、POSTを阻止してしまうとログインができなくなったりしますし、DBの読み取り専用モードでも、認証や監査対応などでデータをなにかしらのテーブルに書き込みが必要なケースもあるあもしれません。
ということで、もう１つ考えられるのが、ユーザのパーミッションを落としてしまう、というもの。

つまり、View xxxxx だけに変更してしまうというものです。

## Viewのパーミッションだけに絞る

Redmineの権限は、ロールベースになっています。Rolesテーブルを見て判断になりますので、ここをいじればよさそうです。
ですが...。

- permissionsという列に権限セットが格納されている
- 正規化はされておらず、1つのフィールドにyaml形式のデータで権限がベタ書きされている

数個〜数十個以上ある権限を調整するのは大変です。なので、こんな感じでやればいいかとおもいます。

### 1. Read Only用のロール作成

View_xxxx (必要に応じてShow xxx) の権限セットを持った、Read Only用のロールを作ります。
設定はGUIの管理画面から実施です。
チェックボックスで選択する権限以外にも、ユーザ表示やプライベートチケットの参照用権限もありますが、今回の件はそちらはいじらないままにします。

![permissions-set.png](https://qiita-image-store.s3.amazonaws.com/0/27976/b9a6ee7f-2765-761e-e818-b420b040f9d1.png "permissions-set.png")



出来上がったpermissionsの内容はこの通り。
実際は、プラグイン導入だと権限セットが増えてしまいますが、最低限プロジェクト、チケット、Wikiの閲覧は可能です。

```
- :view_calendar
- :view_documents
- :view_files
- :view_gantt
- :view_issues
- :view_changesets
- :browse_repository
- :view_time_entries
- :view_wiki_pages
- :view_wiki_edits

```

### 2. その他のロールのpermissionsを修正

さて、メンテナンスでRead Onlyに切り替えたい時間がやってきました。
何をするかというと、そう。ReadOnlyロールと同等の内容にその他のRoleのpermissionsを書き換えてしまうのです。

もしこの対象のRedmineを、メンテナンス後はそのまま使いたいということであれば、権限を元に戻さないといけません。
その場合にそなえて、Rolesテーブルは一時テーブルや別スキーマにバックアップさせておきましょう^^;

以下の権限は、permissions列とは別の列で定義されています。ここを調整してしまうと、たとえばプロジェクトのプライベートチケットが見えなくなったりしてしまいます。ここは更新には関わらないので、そのままにしておきます。

- Issue visibillity
- Time logs visibility
- Users visibility
- All roles managed

#### SQLで修正する場合

DBにも依存しますが、このような感じ。

```
UPDATE roles 
  SET permissions = (SELECT permissions 
                      FROM roles 
                      WHERE id = 6 
                      AND name = 'ReadOnly')
6 rows affected in 33ms

```

![update-by-sql.png](https://qiita-image-store.s3.amazonaws.com/0/27976/64935ada-7ed3-fb79-2a09-b329b0f164c0.png "update-by-sql.png")


#### Consoleで修正してみる場合

rails consoleを利用することもできます。
また、同じサーバのメンテナンスのみでメンテナンス終了後は権限をもとに戻したい場合は、別の作業用のディレクトリに REDMINE_ROOTをコピーしておき、そこからconsoleだけ起動して操作でもいいかもしれません。
（rebootさせるような場合には利用できないので、SQLで実施、権限はバックアップ用テーブルに書き出しておき、あとで戻すのが良いと思います）

```
$ export RAILS_ENV=production
$ bundle exec rails c

# consoleのセッションの中でバックアップ用のオブジェクトを用意
>> backup_permissions = {}
>> Role.find_each { |role| backup_permissions[role.id] = role.permissions }

# 中身確認
>> backup_permissions

# ReadOnlyロールの中身抽出
>> readonly_permissions = Role.find_by_name('ReadOnly').permissions

# 権限変更！
>> Role.find_each { |role| role.permissions = readonly_permissions; role.save! }

# 作業が終わったので権限をもどす
>> Role.find_each { |role| backup_permissions[role.id] = role.permissions }

```

以下は、RubyMineからrails consoleを起動して設定を適用している例です。

![rails-console.png](https://qiita-image-store.s3.amazonaws.com/0/27976/2a4d6790-7c0a-f0a0-d621-532b05e8d7c9.png "rails-console.png")

consoleを利用する場合は、別にunicornやwebrickのプロセス起動（Webのサービスを起動）する必要はありません。
また、もし本番環境のDBと手元の端末が接続できるとか、ポートフォワードでDBとの接続だけでも利用できるなら、ご自身のマシンからrails consoleだけ実行したりもできます。

このあたりは運用の方針（DBや本番環境へのアクセスが厳密に規定されているとか）によって実施できる環境が変わるかと思います。
sshでリモート接続して作業される場合は、screen等でセッションが切れてもrails consoleのプロセスが消えないようにしておくといいかと思います。

### 適用後のサンプル

こちらは、権限を落とした場合：

![readonly-jsmith.png](https://qiita-image-store.s3.amazonaws.com/0/27976/6a2c3312-a9c1-9b67-4326-0e31e4837b47.png "readonly-jsmith.png")

こちらは、権限を戻したあと：

![rollback-jsmith.png](https://qiita-image-store.s3.amazonaws.com/0/27976/0e6f6bfe-6727-66b5-3f23-406b3c138c1c.png "rollback-jsmith.png")


このような感じになります。


### やっぱり運用でカバー＆ユーザさんを信用する場合

さて、思いつく方法を考えてみましたが、やっぱり面倒くさい...。
「書き込みしないでね」というアナウンスは十分に行なったし、ユーザさんの良識を信用してそのままメンテナンスする場合もふくめ、ぜひBanner Pluginをご活用いただけたらと思います。

![use-banner.png](https://qiita-image-store.s3.amazonaws.com/0/27976/a2fd6b47-c18a-bb19-edae-e236fde0d5d4.png "use-banner.png")

[View Customize Plugin](https://redmine.org/plugins/redmine_view_customize)を使って、更新系のボタンをdisabledに置き換えてしまう、というのもありかもしれませんね。

よろしければ、こちらの記事もご覧になってみてくださいね。

- [Redmineのプラグイン「view customize plugin」のカスタマイズ例](http://qiita.com/wfigo7/items/f13a89e1d3522f4a73b6) (@wfigo7さま)
- [Redmineを3年間使い続けてお世話になったプラグインたち](http://qiita.com/Will_meaning/items/97f5ff6b1a3a66fcf975) (@Will_meaningさま)

それでは！

## 備考：Unofficial Redmine Cooking について

Redmineを運用されている方は、少なからず公式サイトでは載っていないような一部改造・カスタマイズするといった経験はお持ちではないかと思います。そういったノウハウを蓄積して、各自がRedmineをより効率的に活用できることを目指したサイトになります。

この記事は、上記サイトの[アドベントカレンダー企画への参加](https://redmine.tokyo/issues/252)として書かせていただきました。

過去の経験を元にしていますので、今ではもっと便利な方法があったり、クラウド環境だから気にしないという方も多いと思います。
「わたしの環境ではこういう方法を取っていますよ！」というのもあれば、ぜひコメントなどいただけると幸いです！
（札束で解決、というのももちろんOK!）