で結局、BEQUEATH VIEWってなんなのさ？(Oracle)

## はじめに

Oracle 12cより VIEWの BEQUEATH属性として DEFINERまたは CURRENT_USERを指定できるようになりました。この属性はその属性値から予想される通り、ストアードプログラムにおける AUTHIDと非常に関連の深い機能です。とはいえ、この新しい属性値にかかわらずVIEW自体の挙動はこれまで通りであり、VIEW内ではVIEW所有者の権限を用いて関連テーブルにアクセスします。ではこの属性によって制御されるのはなにかというと、VIEW内で使用されているファンクションです。マニュアルの記述は以下の通りとなっています。

> [定義者権限および実行者権限のセキュリティの管理](https://docs.oracle.com/cd/E57425_01/121/DBSEG/dr_ir.htm#GUID-2897C310-D0B5-4AB0-8237-384196C4EC81)
>
> ユーザー定義ビューを構成して、ビューで参照される実行者権限関数に対応できます。ユーザーがIDまたは権限依存のSQL関数または実行者権限のPL/SQLまたはJava関数を起動すると、現在のスキーマ、現在のユーザーおよび操作の実行内の現在有効なロールがビューの所有者に設定することなく問い合せたユーザーの環境から継承できます。

なんだかよくわからないですね。マニュアルを要約すると、「実行者権限属性(BEQUEATH CURRENT_USER)のVIEW内で、実行者権限属性(AUTHID CURRENT_USER)のファンクションを使用するとそのファンクションは実行者権限で実行される」ってことです。11gまではできなかったこの動きを理解するには、まずAUTHID属性の『所有者（定義者）権限： DEFINER』および『実行者権限: CURRNET_USER』 を挙動を理解しておく必要があります。



## ストアードプログラムにおけるAUTHID属性

さて、あるユーザがストアードプログラム（プロシジャ、ファンクション）を呼んたとき、そのプログラムはプログラム所有者またはプログラム実行者のどちらの権限で実行されると思いますか？　OSコマンドなどの一般的な感覚ではプログラムは実行者の権限で走ります（SUIDがセットされていない限り）。セキュリティを考えると当然ですね。しかしオラクルでは、「デフォルト」で実行者ではなくプログラム所有者の権限で実行されます。

その理由は簡単で、そちらのほうがデータベースのデータセキュリティ的に都合がよく、不必要なデータ開示を実行者することなく必要なデータだけ提示することができるからです。たとえば、あるファンクションがプログラム所有者の持つテーブルにアクセスして結果を返すとします。もしファンクションが実行者権限で実行されるとなると、そのファンクションはファンクション所有者のもつテーブルにはアクセスできなくなります。このエラーを回避するには実行者にテーブルのアクセスを許可せねばなりません。しかしそうすると実行者はファンクションを経由せずともテーブルデータのすべてを任意で参照することが可能となります。これはまずいですね。

したがって、ストアードプログラムはデフォルトで所有者権限で実行されるように作成されます。しかし、プログラムを実行者の権限で実行するように意図的に設定することもできます。これがAUTHID属性です。実行者権限では、プログラム内で参照されるテーブル等のアクセス権限やファンクション等の実行権限を実行者自身が所有していなければ、当然のことながらエラーとなります。

では、プログラムを実行者権限で実行するアドバンテージはなにでしょうか？　ひとつはパフォーマンスですね。所有者権限の場合、プログラムが実行されるたびに所有者の権限を精査する必要があります。実行者権限の場合、自己の権限がセッションですでに判明しているのでこの動作をスキップできます。したがってアクセス権限の必要のないプログラム、たとえばクエリを発行しない計算ファンクションなどでは実行者権限のほうが有利です。もうひとつは元々権限を持っているユーザのみ実行可にしたい場合でしょうか。ユーザごとに実行許可を与えるのではなくPUBLICに実行許可を与えて誰でも実行自体はできるけれども実際は必要な権限を持っているユーザのみがエラーなく実行できるとかですかね。

### 所有者権限と実行者権限での実行時権限の違い

では簡単に試してみましょう。

まずは、DBAアカウントでPOSSESSORとINVOKERという２つのアカウントを作ります。POSSESSORはオブジェクト所有するのでいくつか権限を与えておきます。

```sql:SYSセッション。ユーザの作成。
-- SYS session

-- POSSESSOR作成
CREATE USER possessor IDENTIFIED BY test
       DEFAULT TABLESPACE users
       QUOTA UNLIMITED ON users;

GRANT CREATE SESSION   TO possessor;
GRANT CREATE TABLE     TO possessor;
GRANT CREATE VIEW      TO possessor;
GRANT CREATE PROCEDURE TO possessor;

-- INVOKER作成
CREATE USER invoker IDENTIFIED BY test
       DEFAULT TABLESPACE users
       QUOTA UNLIMITED ON users;

GRANT CREATE SESSION   TO invoker;
```

つぎに、POSSESSORスキーマにテーブルを作り、そのテーブルにアクセスするファンクションを２つ作ります。ファンクションの内容は同じものですが、ひとつは所有者権限、もう一つは実行者権限にしておきます。

```sql:POSSESSORセッション。テーブルおよびファンクションの作成
-- POSSESSOR session

-- テーブル作成
CREATE TABLE test_tbl
    AS SELECT LEVEL id
       FROM DUAL CONNECT BY LEVEL <= 10;


CREATE OR REPLACE FUNCTION func_definer RETURN NUMBER
AUTHID DEFINER          -- 所有者権限属性
AS
    n NUMBER;
BEGIN
    SELECT COUNT(id) INTO n
    FROM test_tbl;
    RETURN n;
END;
/

CREATE OR REPLACE FUNCTION func_current_user RETURN NUMBER
AUTHID CURRENT_USER     -- 実行者権限属性
AS
    n NUMBER;
BEGIN
    SELECT COUNT(id) INTO n
    FROM test_tbl;
    RETURN n;
END;
/

-- AUTHID属性の確認
SELECT object_name, authid FROM user_procedures;

OBJECT_NAME          AUTHID
-------------------- ------------
FUNC_DEFINER         DEFINER
FUNC_CURRENT_USER    CURRENT_USER


-- INVOKERへ実行権限付与
GRANT EXECUTE ON func_definer      TO invoker;
GRANT EXECUTE ON func_current_user TO invoker;
```

以下の２つのファンクションが作成されました。

| ファンクション名  | 権限       |
| ----------------- | ---------- |
| FUNC_DEFINER      | 所有者権限 |
| FUNC_CURRENT_USER | 実行者権限 |

これらのファンクションを INVOKERユーザから呼んでみます。ファンクションの中身は AUTHID以外は同じです。所有者権限ファンクションは、POSSESSORの権限で実行されているため自身所有のテーブルにアクセスできます。しかし、実行者権限ファンクションは、INVOKERが POSSESSORテーブルにアクセスする権限をもっていないためエラーとなります。

```sql:INVOKERセッション。それぞれのファンクションの実行と結果。
-- INVOKER session

-- 所有者権限ファンクション
SELECT possessor.func_definer FROM DUAL;

FUNC_DEFINER
------------
          10

-- 実行者権限ファンクション
SELECT possessor.func_current_user FROM DUAL;

ORA-00942: table or view does not exist
ORA-06512: at "POSSESSOR.FUNC_CURRENT_USER", line 6
```



### 所有者権限と実行者権限でのロールの扱いの違い

もうひとつ所有者権限と実行者権限で動作が大きく異なるものがあります。それは、ロールの扱いです。所有者権限のプログラムでは実行時ロールは無視されロール経由の権限は無効となります。対して実行者権限のプログラムでは実行時にロール経由の権限も有効です。なぜそんな実装なのか不思議ではありますが、所有者権限では実行時に所有者の権限を確認しなければならないためロール経由の権限まで展開しているとオーバーヘッドが大きくなってしまうということなんでしょうかね。

さらにややこしいことに、所有者権限であろうが実行者権限であろうがプログラム作成時（コンパイル時）にはロール経由の権限は無効となり、実行者権限プログラムでも所有者は直接権限を持っていなければならないとう実装になっていたりします。そういうことなので、ロール権限確認のためのテストプログラムにはダイナミックSQLを発行するEXECUTE IMMEDIATEを使用します。これならば権限がなくてもコンパイル時にエラーになりません。

まず、``v$database``にアクセスする権限をもつロールを作成し、POSSESSORとINVOKERユーザに付与します。

```sql:SYSセッション。ロールの作成と付与
-- SYS session

-- ロールを作成
CREATE ROLE role_test;

GRANT SELECT ON sys.v_$database TO role_test;

-- POSSESSORとINVOKERに付与
GRANT role_test TO possessor;
GRANT role_test TO invoker;
```

POSSESSORユーザは、``v$database``にアクセスすることができますが、``v$database``にアクセスするファンクションを作ろうとするとエラーになります。コンパイル時にはロールが無効になっているからですね。

```sql:POSSESSORセッション。ファンクションの作成試行。
-- POSSESSOR session

SELECT name FROM v$database;

NAME
---------
DB18C

-- v$databaseにアクセスするファンクションは直接権限がないのでエラー
CREATE OR REPLACE FUNCTION func_current_user RETURN VARCHAR2
AUTHID CURRENT_USER
AS
    v varchar2(30);
BEGIN
    SELECT name INTO v
    FROM v$database;
    RETURN v;
end;
/

Warning: Function created with compilation errors.

-------- -----------------------------------------------------------------
1/81     PL/SQL: SQL Statement ignored
1/105    PL/SQL: ORA-00942: table or view does not exist
```

では、EXECUTE IMMEDIATEを経由して``v$database``にアクセスする実行者権限のファンクションを作りって実行してます。ファンクションは実行時もエラーになりません。つまり実行時権限ファンクション内ではロール権限が有効となっているわけです。

```sql:POSSESSORセッション。実行者権限ファンクションでのロール有効確認。
-- POSSESSOR session

CREATE OR REPLACE FUNCTION func_current_user RETURN VARCHAR2
AUTHID CURRENT_USER     -- 実行者権限属性
AS
    v VARCHAR2(30);
BEGIN
    EXECUTE IMMEDIATE 'SELECT name FROM v$database' INTO v;
    RETURN v;
END;
/
Function created.

-- 実行者権限ファンクションはROLEが有効
SELECT func_current_user FROM DUAL;

FUNC_CURRENT_USER
------------------------------
DB18C
```

INVOKERからも試してみます。他ユーザでもROLEが有効なため問題なく実行できています。

```sql:INVOKERセッション。実行者権限ファンクションでのロール有効確認。
-- INVOKER session

SELECT possessor.func_current_user FROM DUAL;

FUNC_CURRENT_USER
------------------------------
DB18C
```

ついでに確認のために所有者権限も試してみます。所有者権限ではロール権限が無効になるため予想通りエラーです。

```sql:POSSESSORセッション。所有者権限ではロール無効確認。
-- POSSESSOR session

CREATE OR REPLACE FUNCTION func_definer RETURN VARCHAR2
AUTHID DEFINER
AS
    v VARCHAR2(30);
BEGIN
    EXECUTE IMMEDIATE 'SELECT name FROM v$database' INTO v;
    RETURN v;
END;
/
Function created.

-- 所有者権限ファンクションはROLEが無効
SELECT func_definer FROM DUAL;

ORA-00942: table or view does not exist
ORA-06512: at "POSSESSOR.FUNC_DEFINER", line 1
```

### 実行者権限プログラムの制限

多少話はそれますが、実行者権限のプログラムにはセキュリティ的に大きな問題があります。権限の強いユーザが他スキーマの実行者権限プログラムを実行すると強い権限のままプログラムが実行されてしまうことです。これは当たり前といえば当たり前の話ではあるのですが、プログラム所有者側が強い権限を持つユーザにプログラムを実行する許可を一方的に与えられてしまうオラクルでは、強い権限を持つユーザ側にも自衛手段が必要です。これが、12cで実装されたINHERIT PRIVILEGES権限です。この権限をつかうことで、実行ユーザ側から許可された実行者権限ファンクションのうち信用できるスキーマにあるもののみを実行できるよう制限することが可能となります。

ただしINHERIT PRIVILEGES権限はユーザが作成された時点でPUBLICに対して付与されます。従ってデフォルトでは（実行許可されあれば）どのスキーマの実行者権限プログラムでも実行できるようになっています。

```sql:INVOKERセッション。INHERIT_PRIVILEGES権限の確認。
-- INVOKER session

SELECT grantee, grantor, privilege
FROM   user_tab_privs
WHERE  privilege = 'INHERIT PRIVILEGES';

GRANTEE                   GRANTOR                   PRIVILEGE
------------------------- ------------------------- -------------------------
PUBLIC                    INVOKER                   INHERIT PRIVILEGES
```

では先程のPOSSESSORの実行者権限ファンクションをINVOKERから実行してみます。INHERIT PRIVILEGES権限を取り消すと、エラーになります。

```sql:INVOKERセッション。INHERIT_PRIVILEGES権限の取消。
-- INVOKER session

SELECT possessor.func_current_user FROM DUAL;

FUNC_CURRENT_USER
------------------------------
DB18C


-- INVOKERのINHERIT PRIVILEGES権限の取り消し
REVOKE INHERIT PRIVILEGES ON USER invoker FROM PUBLIC;

Revoke succeeded.


SELECT possessor.func_current_user FROM DUAL;

ORA-06598: insufficient INHERIT PRIVILEGES privilege
ORA-06512: at "POSSESSOR.FUNC_CURRENT_USER", line 1
```

では、INHERIT PRIVILEGES権限を信頼できるスキーマとしてPOSSESSORのみに与えまず。再びPOSSESSORの実行者権限ファンクションが使用可となりました。

```sql:INVOKERセッション。INHERIT_PRIVILEGES権限の特定ユーザへの付与。
-- INVOKER session


-- INVOKERのINHERIT PRIVILEGES権限をPOSSESSORに付与
GRANT INHERIT PRIVILEGES ON USER invoker TO possessor;

Grant succeeded.


SELECT possessor.func_current_user FROM DUAL;

FUNC_CURRENT_USER
------------------------------
DB18C
```



## VIEWにおけるBEQUEATH属性

ストアードプログラムのAUTHID属性について理解を深めたところで、VIEWのBEQUEATH属性について話をもどします。これは12cからの新機能でAUTHIDと同様DEFINERまたはCURRENT_USERが指定できます。じゃあ、これはVIEWにおける所有者権限と実行者権限なのか？っていうと違います。この属性値にかかわらずVIEWは常に所有者権限でもってテーブル等のオブジェクトにアクセスします。そうでなきゃ使えないですし、もし実行者権限が必要なら実行者のスキーマにVIEWを作ればいいわけなので。BEQUEATH属性は、名前の通りVIEW中で使われているストーアドプログラムに権限をどう伝えるかって話なのです。

たとえば、ある実行者権限ファンクションがあり、あるVIEWで使用されているとします。ユーザがこのVIEWにアクセスすると、VIEWは常に所有者権限です。つまり、せっかく実行者権限でつくったファンクションなのにVIEWを経由すると必ずVIEW所有者権限での実行となってしまうわけです。

そこでこのBEQUEATH属性です。これを実行者権限(CURRENT_USER)にしておくと、そこで使われている実行者権限ファンクションがちゃんと実行者権限で実行されます。もちろんVIEW自体は変わりなく所有者権限でテーブルにアクセスします。

では、確認してみます。12cではどのユーザの権限でプログラムが動いているのか確かめるための疑似カラム``ORA_INVOKING_USER``および``ORA_INVOKING_USERID``が追加されたました。これを用いてAUTHIDとBEQUEATHの各組み合わせでどの権限が実際に使用されているのかを得ています。

```sql:POSSESSORセッション。ファンクションの更新。
-- POSSESSOR session

-- 所有者権限ファンクション
CREATE OR REPLACE FUNCTION func_definer RETURN VARCHAR2
AUTHID DEFINER
AS
    v VARCHAR2(30);
BEGIN
    SELECT ORA_INVOKING_USER INTO v from dual;
    RETURN v;
END;
/

-- 実行者権限ファンクション
CREATE OR REPLACE FUNCTION func_current_user RETURN VARCHAR2
AUTHID CURRENT_USER
AS
    v VARCHAR2(30);
BEGIN
    SELECT ORA_INVOKING_USER INTO v from dual;
    RETURN v;
END;
/
```

INVOKERから実行して確認します。正しい結果を返してくれます。

```sql:INVOKERセッション。各ファンクションの確認。
-- INVOKER session

-- 所有者権限ファンクション
SELECT possessor.func_definer FROM DUAL;
 
FUNC_DEFINER
------------------------------
POSSESSOR                       -- 所有者


-- 実行者権限ファンクション
SELECT possessor.func_current_user FROM DUAL;
 
FUNC_CURRENT_USER
------------------------------
INVOKER                         -- 実行者
```

では、次に同じくINVOKERでAUTHID属性とBEQUEATH属性の異なる４つの組み合わせのVIEWを作ります。

```sql:INVOKERセッション。異なる属性の組わせのVIEWの作成。
-- INVOKER session


-- 実行者権限 / 実行者権限
CREATE OR REPLACE VIEW view_current_current 
BEQUEATH CURRENT_USER
AS 
    SELECT possessor.func_current_user FROM DUAL;

-- 実行者権限 / 所有者権限
CREATE OR REPLACE VIEW view_current_definer
BEQUEATH CURRENT_USER
AS 
    SELECT possessor.func_definer FROM DUAL;

-- 所有者権限 / 実行者権限
CREATE OR REPLACE VIEW view_definer_current
BEQUEATH DEFINER
AS 
    SELECT possessor.func_current_user FROM DUAL;

-- 所有者権限 / 所有者権限
CREATE OR REPLACE VIEW view_definer_definer
BEQUEATH DEFINER
AS
    SELECT possessor.func_definer FROM DUAL;
```

さらに第三者のアカウント(READER)を作ってこれらのVIEWにアクセスし、VIEW内のファンクションが誰の権限で実行されているのか調べてみます。

```sql:SYSおよびREADERセッション。アカウントの作成とファンクションの実行。
--
-- SYS session
--

CREATE USER reader IDENTIFIED BY test;

GRANT CREATE SESSION   TO reader;
GRANT SELECT ANY TABLE TO reader;

--
-- READER session
--

conn reader/test

SELECT * FROM invoker.view_define_current;
SELECT * FROM invoker.view_definer_current;
SELECT * FROM invoker.view_current_definer;
SELECT * FROM invoker.view_definer_definer;
```

結果は以下の通りです。一番上の組み合わせの場合にのみVIEWにアクセスしたアカウントがVIEW内のファンクションまでそのまま伝播され、ファンクションはその権限で実行されます。これが、12cで追加された新しい挙動です。二番目の組み合わせでは、VIEWがDEFINERであるためVIEW所有者の権限にセットされそれがFUNCTIONへと渡ります。これは11gまででの挙動です。下の２つはFUNCTIONがDEFINERであるためBEQUEATHの設定にかかわらずFUNCTION所有者の権限となります。

| VIEW BEQUEATH<BR> (INVOKER) | FUNCTION AUTHID<br>(POSSESSOR) | ORA_INVOKING_USER | 備考           |
| --------------------------- | ------------------------------ | ----------------- | -------------- |
| CURRENT_USER                | CURRENT_USER                   | READER            | VIEWアクセス者 |
| DEFINER                     | CURRENT_USER                   | INVOKER           | VIEW所有者     |
| CURRENT_USER                | DEFINER                        | POSSESSOR         | FUNCTION所有者 |
| DEFINER                     | DEFINER                        | POSSESSOR         | FUNCTION所有者 |

## おわりに

12cの新機能VIEWのBEQUEATH属性の説明でした。いや、別にさして難しことではないんだけど、なぜにオラクルのマニュアルはあんなにわかりにくいのか？？