web-dev-qa-db-ja.com

C ++型メンバーのアクセス許可のようなファイルシステム

要約(tl; dr

質問全体をお読みください。これは非常に単純化されています:
unixファイルのアクセス許可スタイルの制限をタイプ間データ/制御フローに適用して、クラスの一部のグループの一部のクラスメンバーにきめ細かいアクセスを許可するにはどうすればよいですか?

背景情報

UNIXのファイルシステムとアクセス許可について考える場合、ユーザーのファイルアクセス権限をエンコードするさまざまな方法があります(特に [〜#〜] facl [〜#〜] も考慮する場合)。たとえば、ディレクトリに3つのファイルが含まれている場合、それらのファイルは複数のユーザーに属している可能性があり、他のさまざまなユーザーに権限が制限されている可能性があります。

-rwxr-xr--  jean-luc  staff    engage.sh
-rw-r-----  william   crew     roster.txt
-rw-------  beverly   beverly  patients.txt

コアアイデア

ご覧のとおり、特定のユーザーが属しているグループに応じて、さまざまなアクセスレベルが許可されています。たとえば、crewmembersはwilliamに属するroster.txtを読み取ることができますが、crewに属していないと思われるゲストは読み取ることができません。さらに重要なことに、グループcrewには多くの人々を含めることができます。

したがって、型(クラス)をユーザーとして考える場合、C++などのオブジェクト指向言語内のアクセス許可にはいくつかの類似点があると考えていました。関数は実行のみが可能で読み取りはできませんが、rwxフラグはクラスメンバーの意味のある説明を表します。データメンバーは、おそらくアクセサーを介して読み取り(r)および書き込み(w)できますが、メンバー関数は実行される可能性があります(x)またはそうではありません。

ただし、C++やその他のオブジェクト指向言語(私が知っている)では、継承を少しの間省略すれば、これは多かれ少なかれ、ほとんどまたはまったく何もありません。クラスWilliamがメンバーTxt roster;を公開すると、誰でもそれを見ることができます。彼がそれを非公開にすると、自分以外は誰もそれを見ることができません。彼は1人以上の友達friend JeanLuc;を追加できますが、その後、すべてのプライベートメンバーが表示されます(FACL用語ですべてのファイルにuser:jean-luc:rwxを付与するのと同じです)。
これは継承と完全に直交しています-JeanLucWilliamは同じ階層の一部ではなく、関連していません。

したがって、主なアイデアは、プライベート/パブリックの一般化として、グループベースのアクセス制限を許可することです。メンバー関数とメンバーデータへのクラス間のよりきめ細かいアクセスを許可します。

このイディオムは、インタラクションのパーミッションを制限するための追加のファセットを追加するため、保守性/可読性に役立つと思います。オペレーティングシステムと同様に、これによりシステムにセキュリティの重要なレイヤーが追加されますが、同じおなじみのパターンでC++プロジェクトに安全性を追加できます。

C++での表現についての考え

しかし、私はこれを表現する良い方法を考えるのに途方に暮れています。 Williamオブジェクトをサブタイプのいくつかのオブジェクトに分解できます。それぞれのグループを表すWilliam_CrewWilliam_Williamなどです。これはひどく醜いようです。別のアイデアは、次のように個々のグループを表す、フォワーダー関数を備えた専用のタイプである可能性があります。

class Crew { // group class
  // in this group are:
  friend JeanLuc;
  friend Geordi;
  friend Beverly;
  // ...
  static Txt getRoster(William*);
};
class William {
  friend Crew; // Problem: Crew has full access (rwx)
  Txt roster;
};

ただし、各グループは、使用する特定のクラスに合わせて調整する必要があります。これは、グループが複数のユーザー/クラスによって使用されている場合、非常に冗長であるように思われます。

質問

私が提供したアプローチは(穏やかに言えば)素晴らしいものではなく、意図したとおりに機能しないと確信しています。これが小説/愚か/よく知られているアイデアかどうかはわかりませんが、C++言語が提供する機能を使用してこれをどのように実装できるのでしょうか。なぜこれが役立つ/役に立たないかという客観的な議論はありますか?

7
bitmask

問題を少し言い換えると:

  • MySpecialObjectのインスタンスがあります
  • JLPのインスタンスがMySpecialObjectのメソッドを呼び出したい
  • BEVのインスタンスがMySpecialObjectの公開データを読みたい
  • jLPのインスタンスはパブリックデータを読み取ることができず、BEVはメンバー関数を呼び出せないようにする必要があります

これは可能な限りタイプセーフである必要があります。

私が見る解決策はラッパーを含みます:)

  • 通常どおりにMySpecialObjectのコードを記述し、この余分な要件を無視します
  • 制限したいクラスの各側面のラッパーを作成します。 MySpecialObject_ReadMySpecialObject_Executeと言います。
  • これらのラッパーは、リクエスト(メソッド呼び出し、ゲッター/セッター)をMySpecialObjectの基礎となるshared_ptrに転送します
  • MySpecialObjectMySpecialObject_Read、およびMySpecialObject_Executeクラスにはそれぞれ、(必要に応じて)「...に変換する」メソッドがあります。このメソッドは、基になるMySpecialObjectの適切なラッパーを返します

このソリューションは、タイプセーフなアクセサーに必要な制限を提供します。

各クライアントクラスは、必要なアクセスの種類を選択できます。 (そしてyoこれらのクラスを記述するので、yo必要なアクセス権yoが必要です。)それが受け入れられない場合は、作成するだけのファクトリを追加できます「トークン」に応じて特定の制限があるラッパー。このトークンは悪用される可能性がありますが、呼び出し元のクラスインスタンスのRTTI情報である可能性があります。

これは元の問題を解決しますか?

編集:

あなたがプログラマーであることを覚えておいてくださいcanいつでもAをインスタンス化できます。クラスをfriendリストなどに追加することにより...

このソリューションが提供するのは、より明確なインターフェースです。明示的な変換を削除すると、おそらく安全になりますが、柔軟性が低下します。ただし、これらは明示的であるため、コードで簡単に検索して、アーキテクチャのどこかに欠陥がある兆候として扱うことができます。

具体的には、次のようなコードを使用できます。

shared_ptr<A> * a = new A(parameters);

A_read aRead(a);
A_execute aExec(a);
A_write aWrite(aExec);

logger->Log(aRead);
view->SetUserData(aWrite);
controller->Execute(aExec);

ここでは、実行ラッパーと書き込みラッパーの間に明示的な変換がありますが、特定の要件に基づいてこれを決定できます。

しかし、少しの努力(有効な変換を知ること)で、通話場所を見るだけで(自信を持って!)

  • loggerAの状態を変更しません
  • viewAのメソッドを呼び出しません(セッター以外)

これは、それらの特定のメソッド呼び出しが最終的に何百もの他のメソッドを呼び出してしまう場合でも当てはまります。

いくつかの薄いラッパーを犠牲にして、送信するパラメーターを使用して特定の関数呼び出しが何を行うかを一目で確認することができます。これは、調査から一部の分岐を排除するのに役立つため、デバッグ中に非常に役立ち、一般に、プログラムを理解しようとする人々を助けるでしょう。

このACLのアイデアを使用する他の理由を実際に見つけることはできませんでした。少なくとも、コストがメリットを上回らない理由が考えられます。ただし、他の回答で述べられているビジターソリューションよりも直感的に見えます。

2
Andrei

メリットについての評価はお伝えしていませんが、これが行われていない理由だと思います。これにより、オブジェクトシステムが非常に複雑になり、メリットがほとんどなくなります。

ただし、一般に、アクセス許可(r、w、x)はメソッドを使用して明示的に作成されます。パブリックにアクセスできるメンバーを持つのではなく、ゲッターとセッターを通じて明示的な読み取りまたは書き込みアクセスを提供します。

もちろん、これはユーザーまたはグループのモデリングを許可しません(1つのオブジェクトをfriendにすることを除く)。他のプログラミング言語では、同じパッケージ(Java、デフォルト)または同じアセンブリ(C#、friend)内の他のクラスへのアクセスを制限できます。 C++では、プロジェクトを異なるコンパイルユニットに分割し、 コンパイラファイアウォール (別名PIMPL)を使用してクラスの一部の側面へのアクセスをそのコンパイルユニットに制限することで、同様の制限が実現されます。

つまり、ある方法でdoには、さまざまな「ユーザー」グループだけでなく、さまざまなアクセスモードがあります。 FACLに類似した用語の統一モデルでは使用できませんが、同じ効果が得られます。

1
Konrad Rudolph

あなたが考えるどんな解決策も、構文上の複雑さを持っています。それは送信者を知ることのすべてです。何かのようなもの:

_void A::f() { b.method(); }
_

Bは、Aが呼び出し元であることを認識する必要があります。これは、すべての_B::_メソッドをテンプレートに変換し、追加のパラメーターを追加することを意味します。私が考えることができる唯一の考えは、送信者を運ぶある種の「証人」と、しばらくの間「送信者」を保存するためのある種のRAIIオブジェクトを使用することです。

_class target : public requires_permissions<read_permissions<A, B>,
                                           write_permissions<B>,
                                           execute_permissions<>>
{
public:
    // Must call has_read_permissions() as first line (for example)
    int f() const; 

    // Must call has_write_permissions() (for example).
    void g();
};
_

senderクラスは次のようになります。

_// Curiour recursive pattern, to know things about `sender`.
// See later.
class sender : private want_permissions<sender>
{
public:
   void caller();
};

void sender::caller()
{
    // give_me... is a `want_permissions` method.
    scoped_perms sc(give_me_an_access_key_for(my_target)); 

    my_target.f();

    // The give_me method requires my_target inherits from
    // `requires_permissions`, checked at compile time.
    // The key given to `sc` is also sent to `my_target`, in order
    // `requires_permissions` knows which object is asking for
    // using the class, like when calling `f`. my_target's guards 
    // (read/write_permissions()) will take care of the rest (using
    //  the key to know the sender), thowing an exception in case
    // of permission mismatch.

    // `scoped_perms` must allow permissions only for an object
    // at once, to make checking faster and to avoid unintended
    // actions, but must be also recursive: what if this->f() calls
    // this->g() and both requieres permissions? A recursive
    // permission checker!!

    // Of course `scoped_perms` frees the key when is destructed.
}
_

考慮事項:

  • 安全に行う唯一の方法は、_want_permissions_が他のオブジェクトが許可を使用しないようにプライベートに継承することであり、他のオブジェクトがクラスのオブジェクトを作成しないように、すべての_want_permissions_メソッドはprotectedでなければなりません_want_permissions_継承なし。同じ理由で、_want_permissions_は、キーを配信する前に、senderが_want_permissions_から継承されているかどうかを確認する必要があります。

  • keyは設計する必要があります(ポインターを保存するだけで送信者を特定するにはどうすればよいですか?)

  • マルチスレッディングをサポートしたい場合は、一度に異なる承認済みオブジェクトが存在する可能性があるため、状況はさらに難しくなります。キーはコンテナに保存する必要があり、チェックメソッドはそのコンテナを検索する必要があります。問題は、繰り返しますが、一度に複数を保存する場合、特定の瞬間に送信者がどれかわからないことです。おそらく、_thread_local_変数とpImplイディオムを使用すると、何かを実行できます。または、同じtargetを制限するだけで、異なるスレッドから使​​用することはできません。または、ミューテックスを使用して_block_perm_インスタンスが破壊されるまでブロックし、次のオブジェクトを続行できるようにします。

  • 両方のアクセス許可関連の基本クラスは、基本クラスへのポインタなどとして使用されるとは考えられていないため、デストラクタはvirtualである必要はなく、他のvirtualメンバーはありませんなので、すべてのポリモーフィズムのオーバーヘッドが回避されます(これらのクラスにはvtableはありません)。

  • すべてのメソッドが権限を必要とするわけではありません。保護するメソッドにhas_read_permission()has_write_permission()または_has_execution_permission_を追加するだけです。

読み取り/書き込み/実行権限は、タイプコンテナを転送するための単なるタグです。

_template<class... allowed>
struct read_permissions;

template<class... allowed>
struct write_permissions;
_

権限が必要なのは2つのタイプのみで、特殊化を使用してパラメーターパックを抽出します。

_template<class readers_type, class writers_type>
struct requires_permissions;

template<class... readers, class... writers>
struct requires_permissions<read_permissions<readers...>,
                            write_permissions<writers...> >
{
protected:
   void has_read_permissions() const;
   void has_write_permissions() const;

   // other must-be-well-designed stuff

private:
   template<class T*>
   T const* get_key() const;
} 
_

keyは単なる送信者ポインタであり、_get_key_は現在のキーを取得するための(設計する予定の)マジックメソッドであるとします。

_template<class... readers, class... writers>
void requires_permissions<read_permissions<readers...>,
                          write_permission<writers...> >::
has_read_permissions() const
{
   auto* key_ptr = get_key();
   // magic, I said that. It's even possible that it can't be done
   // (with type erasure sure you can).
   // Of course, when creating perm blocks, there should be
   // a way of passing the sender type to this class, in order to
   // get_key() is instantiated in compiler time and 
   // `has_read_permissios` can be a compiler time checker as well,
   // but it requieres a peacefully time to think.

   if (!key_ptr) throw something();

   if (!is_in_pack<decltype(auto), readers...>()) throw something();
}

// no matter where is this function implemented,
// if external or internal:

// They are not specializations each other (partial specializations
// are not allowed for functions). They are two different templates,
// one with at least two template parameters, and other with only one.
template<class guilty, class type, class... suspected>
constexpr bool is_in_pack()
{
    return std::is_same<guilty, type>::value or
      is_in_pack<guilty, suspected...>();
}

template<class guilty> constexpr bool is_in_pack()
{ return false; }
_

それが私があなたに今できるすべてです。

0
Peregring-lk

あなたはあなたが壊れない何かを望んでいないと言います、良い開発者が確立されたルールに従うだけなら何か。私はこれまで提案されてきたことに対してより簡単なアプローチを持っています:

個別の実行または読み取り/書き込み

これを行う簡単な方法は、データとロジックの間でクラスを分割することです。 POJOとJavaistのサービスに似ています。私はそれらをウィリアムとウィリアムエクセキューターと呼びます。

個別の読み取り/書き込み

Constキーワードまたはconst from boostを読み取り専用に使用するだけです。これはオブジェクトの書き込みのみを処理するわけではありませんが、これは本当に必要ですか?

だからあなたはただ持っているでしょう:

class William{
    private: int a;
}
class WilliamExecutor{
    public: void t(William william){}
}

class A{
    // read only
    const Williaw& value;
}

class B{
    //read/write
    William &value;
}
//full
class C{
    William &value;
    WilliamExecutor &executor;

}

これは最も単純なバージョンですが、読み取りアクセスと実行が必要な場合はどうなりますか?

現時点では、WilliamExecutorWilliamへのconst参照を取得できないため不可能です。これは、現在WilliamExecutorがステートレスであるため、シングルトンとして使用できるためです。ただし、その可能性を排除すると、次のように実行できます。

class WilliamExecutor{
     private: William &value;
     public : void test(){}
}

class D{
   private: 
        const William &value;
        WilliamExecutor &exec;
}

//be carefull however, it will be the responsability of the developer who instantiate D to properly have the exec pointing to the same instance of William

new D(williamRef, new WilliamExecutor(williamRef));

これで、書き込み専用(読み取りなし)オブジェクトが必要な場合は、次のように記述された3番目のステートレスクラスWilliamWriterを作成できます。

public class WilliamWriter{
    William &value;
    public void setFoo(String newValue){
        value.setFoo(newValue);
    }
}

そして、クラスWilliamに書き込むだけでよいクラスにそれを渡します。

私はこの問題について@KonradRudolphと同じ意見を持っていますが、クラスの数が少なく、使用の複雑さが少なく、ニーズに合う可能性のある、よりシンプルなソリューションを提供したいと考えていました。

たぶん、これをいくつかのジェネリックで適応させることは可能ですが、私よりもc ++をマスターしている人々にこれを許可します。

注:メソッドを別のクラスに移動することは、OOPを壊すと見なすことができます。それは本当かもしれませんが、私はあなたが何かを使える、つまり実行可能な、そして人々が使いたいと思うものを手に入れることができるとは思いません。

0
Walfrat

わかりました解決策はビジターパターンを実装することです:

struct Crew 
{
    virtual void setRoster(Txt roster) = 0;
    virtual ~Crew(){}
};

class William 
{
public:
    void getRoster(Crew& crew) { crew.setRoster(roster); }
private:
    Txt roster;
}

誰かがCrewを実装しているため、「Crewグループに参加します」。

[〜#〜] edit [〜#〜]より一般的なケースが必要な場合は、いくつかのセキュリティ記述子を作成できますが、複雑すぎると思います。

class  CrewDescriptorRead
{
protected:
    CrewDescriptorRead(){}
    friend class User;
};

class CrewDescriptorWrite
{
protected:
    CrewDescriptorWrite(){}
    friend class User;
};

class CrewDescriptorFullAccess: public CrewDescriptorRead, public CrewDescriptorWrite
{
public:
    CrewDescriptorFullAccess(const CrewDescriptorRead&, const CrewDescriptorWrite&){}
};



class William 
{
public:
    Txt getRoster(const CrewDescriptorRead& ) const {return roster;}
private:
    Txt roster;
};


struct User
{
    void f()
    {
        William w;
        w.getRoster(CrewDescriptorFullAccess(CrewDescriptorRead(), CrewDescriptorWrite()));
    }
};

struct UnAutorized
{
    void f()
    {
        William w;
        w.getRoster(CrewDescriptorFullAccess(CrewDescriptorRead(), CrewDescriptorWrite()));
        //OOps, I'm not friend
    }
};

または、もしあなたが絶対にパラノイドなら、

class  CrewDescriptorRead
{
private:
    CrewDescriptorRead(){}
    friend class User;
    friend class CrewDescriptorFullAccess;
};

class CrewDescriptorWrite
{
private:
    CrewDescriptorWrite(){}
    friend class User;
    friend class CrewDescriptorFullAccess;
};

class CrewDescriptorFullAccess
{
public:
    CrewDescriptorFullAccess(const CrewDescriptorRead&, const CrewDescriptorWrite&){}
    operator CrewDescriptorRead() { return CrewDescriptorRead(); }
    operator CrewDescriptorWrite() { return CrewDescriptorWrite(); }
};
0
Lol4t0