Access Controlは、プログラムの特定の部分をアクセス可能/可視にし、他の部分をアクセス不可/不可視にすることができます。
Cadenceでは、Access Controlは次の要素で構成されます。
1. Capabilityセキュリティを使用したアカウントストレージ内のオブジェクトに対するアクセス制御
ユーザーは、オブジェクトを所有しているか、そのオブジェクトへの参照を持っている場合を除き、そのオブジェクトにアクセスできません。つまり、デフォルトでは、本当にパブリックなものは何もありません。
他のアカウントは、アカウントの所有者がオブジェクトへの参照を提供してアクセス権を付与しない限り、アカウント内のオブジェクトを読み書きできません。
このようなアクセス制御については、CapabilitiesおよびCapability Managementのページで説明しています。
2. アクセス修飾子(access
キーワード)を使った、コントラクトおよびオブジェクト内のアクセス制御
このページでは、アクセス修飾子を使用したアクセス制御の第2部について説明します。
関数(function)、複合型(composite types)、フィールドなどのすべての宣言には、access
キーワードを使用してアクセス修飾子を指定する必要があります。
アクセス修飾子は、その宣言がどの場所でアクセス可能/可視であるかを決定します。フィールドは、同じスコープ内またはその内部スコープ内でのみ代入および変更が可能です。
例として、関数をパブリックにアクセス可能にするには(access(all)
については後述します)
access(all)
fun test() {}
Access Controlには5段階のレベルがあります。
-
Public accessは、宣言がすべてのスコープでアクセス可能/可視であることを意味します。
これには、外部スコープ、現在のスコープ、内部スコープが含まれます。access(all)
修飾子を使用することで宣言をパブリックアクセス可能にします。
例えば、ある型の中のパブリックフィールドは、外部スコープの中にある型のインスタンス上からアクセスすることができます。 -
Entitled access(エンタイトルドアクセス)は、宣言がオブジェクトの所有者、または必要な権限を付与された参照に対してのみアクセス可能/可視であることを意味します。
宣言は、access(E)
構文を使用したEntitlementによりアクセス可能になります。ここでE
は1つ以上のEntitlementセット、または単一のEntitlementマッピングです。
参照の型のauth
部分にそのEntitlementが記載されている場合、参照はそのEntitlementが承認されているとみなされます。
例えば、R
リソース上のaccess(E, F)
フィールドは、所有している@R
型の値、またはE
およびF
の権限が承認(auth(E, F) &R
)されたR
の参照によってのみアクセスできます。 -
Account access(アカウントアクセス)は、宣言が定義されているアカウントのスコープ内でのみアクセス可能/可視であることを意味します。つまりアカウント内の他のコントラクトからはその宣言にアクセスできることを意味します。
access(account)
キーワードを使用することで、宣言は同じアカウント内のコード、例えば、アカウントの持つ他のコントラクトからのアクセスが可能になります。 -
Contract access(コントラクトアクセス)は、宣言が定義されているコントラクトのスコープ内でのみアクセス可能/可視であることを意味します。つまり同じコントラクト内で定義された他の宣言からはアクセス可能ですが、同じアカウント内の他のコントラクトからはアクセスができないことを意味します。
access(contract)
キーワードを使用することで、宣言は同じコントラクト内のコードからのアクセスが可能になります。 -
Private access(プライベートアクセス)は、現在のスコープおよび内部スコープでのみ宣言にアクセス可能/可視であることを意味します。
access(self)
キーワードを使用することで、同じ型を包含するスコープ内のコードからのみ宣言に対してアクセス可能になります。
例えば、access(self)
フィールドはその型と同一のスコープ内にある関数からしかアクセスできず、外部スコープのコードからはアクセスできません。
変数宣言、定数宣言、フィールドの動作をまとめると、以下のようになります。
宣言の種類 | Access modifier | Accessible in | Assignable in |
---|---|---|---|
let |
access(self) |
Current and inner | None |
let |
access(contract) |
Current, inner, and containing contract | None |
let |
access(account) |
Current, inner, and other contracts in same account | None |
let |
access(all) |
All | None |
let |
access(E) |
All with required entitlements | None |
var |
access(self) |
Current and inner | Current and inner |
var |
access(contract) |
Current, inner, and containing contract | Current and inner |
var |
access(account) |
Current, inner, and other contracts in same account | `Current and inner |
var |
access(all) |
All | Current and inner |
var |
access(E) |
All with required entitlements | Current and inner |
Composite Types(Struct,リソース)の宣言はパブリックでしか出来ません。ただし、宣言や型が公に可視であっても、リソースは宣言されたスマートコントラクト内でのみでしか作成できません。また、イベントも宣言されたスマートコントラクト内でのみでしか発行できません。
// Declare a private constant, inaccessible/invisible in outer scope.
//
access(self)
let a = 1
// Declare a public constant, accessible/visible in all scopes.
//
access(all)
let b = 2
// Declare a public struct, accessible/visible in all scopes.
//
access(all)
struct SomeStruct {
// Declare a private constant field which is only readable
// in the current and inner scopes.
//
access(self)
let a: Int
// Declare a public constant field which is readable in all scopes.
//
access(all)
let b: Int
// Declare a private variable field which is only readable
// and writable in the current and inner scopes.
//
access(self)
var c: Int
// Declare a public variable field which is not settable,
// so it is only writable in the current and inner scopes,
// and readable in all scopes.
//
access(all)
var d: Int
// Arrays and dictionaries declared without (set) cannot be
// mutated in external scopes
access(all)
let arr: [Int]
// The initializer is omitted for brevity.
// Declare a private function which is only callable
// in the current and inner scopes.
//
access(self)
fun privateTest() {
// ...
}
// Declare a public function which is callable in all scopes.
//
access(all)
fun publicTest() {
// ...
}
// The initializer is omitted for brevity.
}
let some = SomeStruct()
// Invalid: cannot read private constant field in outer scope.
//
some.a
// Invalid: cannot set private constant field in outer scope.
//
some.a = 1
// Valid: can read public constant field in outer scope.
//
some.b
// Invalid: cannot set public constant field in outer scope.
//
some.b = 2
// Invalid: cannot read private variable field in outer scope.
//
some.c
// Invalid: cannot set private variable field in outer scope.
//
some.c = 3
// Valid: can read public variable field in outer scope.
//
some.d
// Invalid: cannot set public variable field in outer scope.
//
some.d = 4
// Invalid: cannot mutate a public field in outer scope.
//
some.f.append(0)
// Invalid: cannot mutate a public field in outer scope.
//
some.f[3] = 1
// Valid: can call non-mutating methods on a public field in outer scope
some.f.contains(0)
Entitlements
Entitlementは、Composite Types(Struct,リソース)メンバ(フィールドおよび関数)に粒度に応じたアクセス制御を提供します。Entitlementは、entitlement E
という構文を使用して宣言され、E
にはEntitlementの名前が入ります。
例えば、次のコードでは、EとFという2つのEntitlementを宣言しています。
entitlement E
entitlement F
Entitlementは他のスマートコントラクトからインポートし、他の型と同じように使用することができます。他のスマートコントラクトで定義されたEntitlementの場合は、他の型と同じ修飾名の構文が使用されます。
contract C {
entitlement E
}
C
の外側ではE
はC.E
の構文で使用されます。
Entitlement(権限)は型と同じ名前空間内に存在するため、コントラクトでリソースをR
と宣言した場合、同じくR
と呼ばれるEntitlement(権限)を宣言することはできません。
Entitlement(権限)はComposite Types(Struct,リソース)メンバ(フィールドおよび関数)のアクセス修飾子として使用でき、それらのComposite Typesへの参照が、どのメンバへのアクセスを許可するかを指定します。
アクセス修飾子は複数の権限を含めることが出来、|
で結合された論理和「or」を示すか、または ,
で結合された論理積「and」を示すことができます。 2種類のセパレータを同じセットで組み合わせることはできません。
例えば:
access(all)
resource SomeResource {
// requires a reference to have an `E` entitlement to read this field
access(E)
let a: Int
// requires a reference to have either an `E` OR an `F` entitlement to read this field.
access(E | F)
let b: Int
// requires a reference to have both an `E` AND an `F` entitlement to read this field
access(E, F)
let c: Int
// intializers omitted for brevity
// ...
}
リソースの型または参照型の以下の定数値が存在すると仮定します。
let r: @SomeResource = // ...
let refE: auth(E) &SomeResource = // ...
let refF: auth(F) &SomeResource = // ...
let refEF: auth(E, F) &SomeResource = // ...
let refEOrF: auth(E | F) &SomeResource = // ...
参照は以下のように使用できます。
// valid, because `r` is owned and thus is "fully entitled"
r.a
// valid, because `r` is owned and thus is "fully entitled"
r.b
// valid, because `r` is owned and thus is "fully entitled"
r.c
// valid, because `refE` has an `E` entitlement as required
refE.a
// valid, because `refE` has one of the two required entitlements
refE.b
// invalid, because `refE` only has one of the two required entitlements
refE.c
// invalid, because `refF` has an `E` entitlement, not an `F`
refF.a
// valid, because `refF` has one of the two required entitlements
refF.b
// invalid, because `refF` only has one of the two required entitlements
refF.c
// valid, because `refEF` has an `E` entitlement
refEF.a
// valid, because `refEF` has both of the two required entitlements
refEF.b
// valid, because `refEF` has both of the two required entitlements
refEF.c
// invalid, because `refEOrF` might not have an `E` entitlement (it may have `F` instead)
refEOrF.a
// valid, because `refEOrF` has one of the two entitlements necessary
refEOrF.b
// invalid, because `refEOrF` is only known to have one of the two required entitlements
refEOrF.c
この例では、リソースを保持する値r
が、SomeResource
上のすべての権限(Entitlement)のメンバにアクセスできることに特に注目してください。リソースを保持する値は権限(Entitlement)の宣言の影響を受けません。
権限の参照の詳細については、こちらをご覧ください。
Entitlement mappings
権限マッピングは、ネストされた階層構造において親オブジェクトから子オブジェクトに権限がどのように伝達されるかを静的に宣言する方法です。
オブジェクトが子オブジェクトのフィールドを持つ場合、権限マッピングを用いることで、親オブジェクトに対する参照の権限(Entitlement)に基づき、子オブジェクトにアクセス権を付与することができます。
内部リソースへのアクセスを制御するためにEntitlement(権限)を使用する次の例を考えてみましょう。
entitlement OuterEntitlement
entitlement InnerEntitlement
resource InnerResource {
access(all)
fun foo() { ... }
access(InnerEntitlement)
fun bar() { ... }
}
resource OuterResource {
access(self)
let childResource: @InnerResource
init(childResource: @InnerResource) {
self.childResource <- childResource
}
// The parent resource has to provide two accessor functions
// which return a reference to the inner resource.
//
// If the reference to the outer resource is unauthorized
// and does not have the OuterEntitlement entitlement,
// the outer resource allows getting an unauthorized reference
// to the inner resource.
//
// If the reference to the outer resource is authorized
// and it has the OuterEntitlement entitlement,
// the outer resource allows getting an authorized reference
// to the inner resource.
access(all)
fun getPubRef(): &InnerResource {
return &self.childResource as &InnerResource
}
access(OuterEntitlement)
fun getEntitledRef(): auth(InnerEntitlement) &InnerResource {
return &self.childResource as auth(InnerEntitlement) &InnerResource
}
}
このパターンでは、OuterResource
がInnerResource
を格納することが可能であり、そして、そのネストされたリソースにアクセスする方法を、保有するEntitlement次第でさまざまに作成することができます。
OuterResource
の未承認の参照は、getPubRef
関数のみ呼び出すことができ、したがって未承認のInnerResource
の参照のみ取得できます。 InnerResource
の参照は、パブリックにアクセス可能なfoo
関数のみ実行を許可しますが、bar
関数の実行はできません。 これを実行するにはInnerEntitlement
を必要としますが、それが与えられていないためです。
しかし、OuterEntitlement
で承認されたOuterResource
の参照は、getEntitledRef
関数を実行でき、この関数はInnerEntitlement
で承認されたInnerResource
への参照を返します。この参照を使用して、bar
関数を実行することができます。
このパターンは機能しますが、残念ながらInnerResource
に対すしての全てのアクセッサー関数は"複製"しなければなりません。
権限マッピングはこの複製を回避するに使用されます。
権限マッピングは、以下の構文を使用して宣言します。
entitlement mapping M {
// ...
}
M
はマッピングの名前です。
マッピングの本体(body)には、A -> B
の形式のルールをゼロ個以上含みます。ここで、A
およびB
は権限(Entitlement)です。各ルールは、左側に権限の参照が与えられた場合、右側に権限を持った参照が生成される、ことを宣言しています。
権限マッピングは、そのように関数、入力に(ドメインと呼ばれる)Entitlementのセットを渡し、(レンジまたはイメージと呼ばれる)Entitlementのセットを出力する関数を定義しています。
権限マッピングを使用すると、上術の例はより簡潔に、このように記述できます。
entitlement OuterEntitlement
entitlement InnerEntitlement
// Specify a mapping for entitlements called `OuterToInnerMap`,
// which maps the entitlement `OuterEntitlement` to the entitlement `InnerEntitlement`.
entitlement mapping OuterToInnerMap {
OuterEntitlement -> InnerEntitlement
}
resource InnerResource {
access(all)
fun foo() { ... }
access(InnerEntitlement)
fun bar() { ... }
}
resource OuterResource {
// Use the entitlement mapping `OuterToInnerMap`.
//
// This declares that when the field `childResource` is accessed
// using a reference authorized with the entitlement `OuterEntitlement`,
// then a reference with the entitlement `InnerEntitlement` is returned.
//
// This is equivalent to the two accessor functions
// that were necessary in the previous example.
//
access(mapping OuterToInnerMap)
let childResource: @InnerResource
init(childResource: @InnerResource) {
self.childResource <- childResource
}
// No accessor functions are needed.
}
// given some value `r` of type `@OuterResource`
let pubRef = &r as &OuterResource
let pubInnerRef = pubRef.childResource // has type `&InnerResource`
let entitledRef = &r as auth(OuterEntitlement) &OuterResource
let entitledInnerRef = entitledRef.childResource // has type `auth(InnerEntitlement) &InnerResource`,
// as `OuterEntitlement` is defined to map to `InnerEntitlement`.
// `r` is an owned value, and is thus considered "fully-entitled" to `OuterResource`,
// so this access yields a value authorized to the entire image of `OuterToInnerMap`,
// in this case `InnerEntitlement`, and thus can call `bar`
r.childResource.bar()
権限マッピングは、アクセッサ関数(上記の例)の中で使用されたり、 フィールド(参照やContainer(ディクショナリ、配列)の型であることが条件)の中で使用されます。
権限マッピングは1:1である必要はありません。多くの入力が同じ出力にマッピングされるマッピングや1つの入力に対し複数の出力がマッピングされるマッピングを定義することも可能です。
権限マッピングは、それらがマッピングを行うセットの「種類」を保持します。つまり、論理積(and)セットのマッピングは、論理積セットを生成し、論理和(or)セットのマッピングは、論理和セットを生成します。
同じセットに組み合わされるEntitlementセパレータはないため、論理和(or)セットを特定の複雑なマッピングでマッピングしようとすると、タイプエラーが発生することがあります。
例えば、以下の権限マッピングがあるとします。
entitlement mapping M {
A -> B
A -> C
D -> E
}
(A | D)
をM
を介してマッピングしようとすると、失敗します。なぜなら、A
は(B, C)
にマッピングされるべきであり、D
はE
にマッピングされるべきですが、この2つの出力は論理和(or)セットには組み合わせられないからです。
権限マッピングの使用方法の好例として、Account typeがあります。
The Identity entitlement mapping
Identity
は、すべてのインプットをそのまま出力する特別な組み込み(built-in)の権限マッピングです。Identity
マップを通過した権限セットは、アウトプットでは変更されません。
例えば:
entitlement X
resource InnerResource {
// ...
}
resource OuterResource {
access(mapping Identity)
let childResource: @InnerResource
access(mapping Identity)
getChildResource(): auth(mapping Identity) &InnerResource {
return &self.childResource
}
init(childResource: @InnerResource) {
self.childResource <- childResource
}
}
fun example(outerRef: auth(X) &OuterResource) {
let innerRef = outerRef.childResource // `innerRef` has type `auth(X) &InnerResource`,
// as `outerRef` was authorized with entitlement `X`
}
Identity
のマッピングで一つ注意すべき大切な点としては、そのアウトプット範囲が不明であり、理論的には無限であるということです。そのため、Identity
マッピングされたフィールドまたは関数にリソースの実物の値でアクセスすると、空のアウトプットセットが生成されます。
例えば、実物のOuterResource
値に対してgetChildResource()
を呼び出すと、認証されていない&InnerResource
参照が生成されます。
Mapping composition
権限マッピングは構成可能です。 権限マッピングの定義では、1つまたは複数の他のマッピングの定義を含めることができ、それらのマッピング関係をコピーすることができます。権限マッピングは、include M
構文を使用して別の権限マッピングを含めることができます。ここで、M
は含める権限マッピングの名前です。
一般的に、権限マッピングの定義におけるinclude M
ステートメントは、N
にM
で定義された全ての関係を単純にコピー&ペーストすることと同等です。
include
のサポートは、主にコードの再利用を減らし、構成を促進する目的で提供されています。
例えば:
entitlement mapping M {
X -> Y
Y -> Z
}
entitlement mapping N {
E -> F
}
entitlement mapping P {
include M
include N
F -> G
}
権限マッピングP
は、M
およびN
で定義された全ての関係と、それ自身で定義された追加の関係の定義を含みます。
Identity
マッピングを含めることも可能です。Identity
マッピングを含む全てのマッピングM
は、それに対する入力セットを、マッピングの中で定義された追加の関係や、他にインクルードされたマッピングの中で定義された追加の関係を一緒に、それ自身にマッピングします。
例えば:
entitlement mapping M {
include Identity
X -> Y
}
マッピングM
は、権限セット(X)
を(X, Y)
に、(Y)
を(Y)
にマッピングします。
シクリカルマッピングを生成するincludeは、型チェッカーによって却下されます。
Built-in mutability entitlements
Entitlementの代表的なユースケースは、可変性によるオブジェクトへのアクセスを制御することです。
例えば、配列やディクショナリなど複合型では、制作者は特定のフィールドへのアクセスを読み取り専用に制御し、一部のフィールドを可変に出来るようにしたい場合があります。
これをサポートするために、以下のビルトインのEntitlementを使用できます。
Insert
Remove
Mutate
これらは主にビルトインの配列およびディクショナリの関数で使用されますが、任意の宣言のアクセス修飾子で利用可能です。
CadenceはEntitlementの合成や継承をサポートしていませんが、Mutate
Entitlementは、(Insert, Remove)
の権限セットの結合と同等の形として利用されることを意図しています。
翻訳元->https://cadence-lang.org/docs/language/access-control
Flow BlockchainのCadence version1.0ドキュメント (Access control)