17. Security

認証と承認の2段階のセキュリティ定義があります。

17.1. 認証(Authentication)

認証機能は要件に応じたユーザ管理等の個別の実装が必要になる機能のため、使用していません。
また、外部サービスを利用して認証を行う、account_linker については、対話処理とは別に実施すべき機能のため使用できません。
別途、必要な認証手続きを上位のアプリケーション等で行った上、対話処理を実施される事を推奨します。
以下に、認証に関する基本的な制御構造について説明します。
認証はコアコードのbrain内で実施され、ユーザからの発話文毎に処理される ask_question() 内で呼び出されます。
コンフィグレーションで指定された認証サービスを利用して、認証されたユーザのみが対話処理を実施できる様に制御します。
class Brain(object):

    def __init__(self, bot, configuration: BrainConfiguration):
       self._security = SecurityManager(configuration.security)

    def authenticate_user(self, client_context):
        return self._security.authenticate_user(client_context)

    def ask_question(self, bot, clientid, sentence):

        authenticated = self._security.authenticate_user(client_context)
        if authenticated is not None:
            return authenticated
認証サービスは、コンフィグレーションの定義に従って、SecurityManagerによってロードされるクラスで、その基底クラスは次のように定義されています。
SecurityManagerの authenticate_user() を実施することで、認証サービス内の authenticate() が呼び出されます。
class Authenticator(object):

    def __init__(self, configuration: BrainSecurityConfiguration):
        self._configuration = configuration

    @property
    def configuration(self):
        return self._configuration

    def get_default_denied_srai(self):
        return self.configuration.denied_srai

    def authenticate(self, clientid: str):
        return False

以下の認証サービスの例では、対話処理のアプリケーションの識別名となる’clientid’と、個々のユーザの識別名’userid’で制御しています。 この実装では、clientid:’console’でアクセスされたユーザの’userid’を’authorised’リストで管理して、認証を行うシンプルなものです。

class ClientIdAuthenticationService(Authenticator):

    def __init__(self, configuration: BrainSecurityConfiguration):
        Authenticator.__init__(self, configuration)
        self.authorised = [
            "console"
        ]

    def user_auth_service(self, client_context):
        return False

    def _auth_clientid(self, client_context):
        authorised = self.user_auth_service(client_context)
        if authorised is True:
            self.authorised.append(client_context.userid)
        return authorised

    def authenticate(self, client_context):
        try:
            if client_context.userid in self.authorised:
                return True
            else:
                if self._auth_clientid(client_context) is True:
                    return True

                return False
        except Exception as excep:
            YLogger.error(client_context, str(excep))
            return False

上記機能を使用するには、Brainコンフィグレーションの security で、以下の項目を定義する必要があります。

brain:
    security:
        authentication:
            classname: programy.security.authenticate.clientidauth.ClientIdAuthenticationService

17.2. 承認(Authorisation)

承認は、templateの authorise 要素において、配下にある各要素の展開を制御する為に行います。
承認も認証と同じく要件に応じた処理が必要であり、変更を可能にする為、コンフィグレーションで指定された承認サービスを利用する方式で行います。

authorise 要素での承認処理は、以下の様に行っています。

class TemplateAuthoriseNode(TemplateNode):

    def resolve_to_string(self, client_context):

        if client_context.brain.security.authorisation is not None:
            try:
                allowed = client_context.brain.security.authorisation.authorise(client_context.userid, self.role)
            except AuthorisationException:
                allowed = False

承認サービスは、コンフィグレーションの定義に従って、brain.security.authorisation にロードされるクラスで、その基底クラスは次のように定義されています。

class Authoriser(object):

    def __init__(self, configuration: BrainSecurityConfiguration):
        self._configuration = configuration

    @property
    def configuration(self):
        return self._configuration

    def get_default_denied_srai(self):
        return self.configuration.denied_srai

    def authorise(self, userid, role):
        return False

以下に、authorise要素で使用している、ユーザの識別名’userid’を元に行う承認処理を説明します。

シナリオでの承認を用いた記載は、利用制限をかけるtemplate要素を、authorise タグで囲むことで指定します。
以下のシナリオの場合、発話文で’ALLOW ACCESS’が入力された場合、指定された’userid’が ‘root’ の権限(role)を持っている場合、’Access Allowed’ の文字が変え入りますが、持っていない場合には空文字が返ります。
<category>
    <pattern>ALLOW ACCESS</pattern>
    <template>
        <authorise role="root">
            Access Allowed
        </authorise>
    </template>
</category>

承認処理を行うクラスの実装は以下になります。

class BasicUserGroupAuthorisationService(Authoriser):

    def __init__(self, config: BrainSecurityAuthorisationConfiguration):
        Authoriser.__init__(self, config)
        self._users = {}
        self._groups = {}

    @property
    def users(self):
        return self._users

    @property
    def groups(self):
        return self._groups

    def initialise(self, client):
        self.load_users_and_groups(client)

    def load_users_and_groups(self, client):
        if client.storage_factory.entity_storage_engine_available(StorageFactory.USERGROUPS) is True:
            storage_engine = client.storage_factory.entity_storage_engine(StorageFactory.USERGROUPS)
            usergroups_store = storage_engine.usergroups_store()
            usergroups_store.load_usergroups(self)
        else:
            YLogger.warning(self, "No user groups defined, authorisation tag will not work!")

    def authorise(self, userid, role):
        if userid not in self._users:
            raise AuthorisationException("User [%s] unknown to system!" % userid)

        if userid in self._users:
            user = self._users[userid]
            return user.has_role(role)
        return False

尚、本機能を使用する為、Brainコンフィグレーションの security で、以下の項目を定義する必要があります。

security:
    authorisation:
        classname: programy.security.authorise.usergroupsauthorisor.BasicUserGroupAuthorisationService
        denied_srai: AUTHORISATION_FAILED
        denied_text: Access Denied!

 ※ denied_sraidenied_text は、認証失敗時の動作に関するオプションです。

17.2.1. ユーザグループファイル

承認処理を制御するために、ユーザ(userId)と権限(role)の関係を定義するものが、Storageの usergroupsエンティティ で指定したファイルになります。
定義は、yaml形式で行います。基本的な記述形式は、以下の2つの形式になります。

1つ目は、ユーザ毎に権限を記載する方式です。

users:
  ユーザ名:
    roles: 権限名リスト

2つ目は、グループ名を規定し、グループ毎に権限を指定し、該当するユーザ名を列記する方式です。

groups:
  グループ名:
    roles: 権限名リスト
    users: ユーザ名リスト

以下の例では、’administrator’の権限は’rootと’user’、’others’の権限は’user’、そして、’guest1’と’guest2’の権限は’guest’になります。

設定例

users:
  administrator:
    roles: root, user
  others:
    roles: user

groups:
   general:
      users: guest1, guest2
      roles: guest
また、groupsを子タグで指定することで関係付けができるため、以下の様な指定も可能です。
この場合には、結果的に、’console’の権限は’user’に加えて、’root’、’admin’、’system’、’ask’が設定されます。

設定例

users:
  console:
    roles: user
    groups: sysadmin

groups:
  sysadmin:
    roles: root, admin, system
    groups: user
  user:
    roles: ask
尚、グループを関係付けて記述した場合、最終的にユーザに対する権限が指定されないことがあります。基本的な記法をベースとしたシンプルな記述を推奨します。
又、記述純情に関係なく``users`` 定義の展開を先に行う為、users 定義でユーザの権限を指定している場合、groups 定義で同じユーザに別の権限を指定しても、users での指定が優先されます。