---
# System prepended metadata

title: 'Per Tenant Setting [Traction]'

---

# Per Tenant Setting [Traction]

### Resolves
- Issue [Traction #540](https://github.com/bcgov/traction/issues/540)
With [issue #394](https://github.com/bcgov/traction/issues/394), support the ability to allow a tenant to connect to endorser and/or create a public DID. With [issue #393](https://github.com/bcgov/traction/issues/393), based on the description [here](https://github.com/bcgov/traction/blob/develop/docs/USE-CASE-ONBOARD.md#make-teanant-an-issuer), this will require connection to endorser and creating/registering a public did [covered above]

### Design
- Update `InnkeeperWalletConfig` to allow the following bool settings/flags:
  - `connect_to_endorser`
  - `create_public_did`
  - `issuer`
- If `issuer` option is set as True then both `connect_to_endorser` and `create_public_did` are set as True
- `create_innkeeper` function in `TenantManager` is where these additional flags in config are processed and accordingly new utility functions [`connect_to_endorser` and `create_public_did`] are called upon.

### Implementation
In `plugins/traction_innkeeper/traction_innkeeper/v1_0/innkeeper/config.py`

1. Update `InnkeeperWalletConfig`
```
class InnkeeperWalletConfig(BaseModel):
    tenant_id: Optional[str]  # real world, this is a UUID
    wallet_name: Optional[str]
    wallet_key: Optional[str]
    print_key: bool = False
    print_token: bool = False
    connect_to_endorser: bool = False
    create_public_did: bool = False
    issuer: bool = False

    class Config:
        alias_generator = _alias_generator
        allow_population_by_field_name = True

    @classmethod
    def default(cls):
        # consider this for local development only...
        return cls(
            tenant_id="innkeeper",
            wallet_key="MySecretKey123",
            wallet_name="traction_innkeeper_v1_0",
            print_key=False,
            print_token=False,
            connect_to_endorser=False,
            create_public_did=False,
            issuer=False,
        )
```

In `plugins/traction_innkeeper/traction_innkeeper/v1_0/innkeeper/tenant_manager.py`

1. add the following functions [note: create_public_did() is incomplete]:

```
def process_per_tenant_issuer_settings(self, config: InnkeeperWalletConfig):
    if not config.issuer:
        return config.connect_to_endorser, config.create_public_did
    else:
        return True, True


async def connect_to_endorser(self):
    endorser_srv = self._profile.inject_or(EndorserConnectionService)
    if not endorser_srv:
        raise Exception(
            "No EndorserConnectionService instance is binded with the tenant profile."
        )
    info = endorser_srv.endorser_info(self._profile)
    if not info:
        raise Exception("Endorser is not configured")
    await endorser_srv.connect_with_endorser(self._profile, self._profile.context.injector)


async def create_public_did(self, method="sov", key_type="ed25519", seed=None, did=None):
    # Sub wallet
    wallet = self._profile.inject_or(BaseWallet)
    if not wallet:
        raise Exception(
            "No subwallet found or BaseWallet instance is binded with the tenant profile."
        )
    # Creating local DID
    try:
        await wallet.create_local_did(
            method=method, key_type=key_type, seed=seed, did=did
        )
    except WalletError as err:
        self._logger.error(err)
        raise err
    # Registering DID
    # Derive code from register_ledger_nym function in /ledger/register-nym [ACAPy]

    # Assign DID as public DID
    # Derive code from wallet_set_public_did function in /wallet/did/public [ACAPy]
```
2. Update `create_innkeeper` function:
```
async def create_innkeeper(self):
    config = self._config.innkeeper_wallet
    tenant_id = config.tenant_id
    wallet_name = config.wallet_name
    wallet_key = config.wallet_key
    connect_to_endorser, create_public_did = self.process_per_tenant_issuer_settings(config)
    if connect_to_endorser:
        await self.connect_to_endorser()
    if create_public_did:
        await self.create_public_did()
    ...
```

# Per Tenant Setting [ACAPy]
### Resolves
- Issue [Traction #547](https://github.com/bcgov/traction/issues/547)
- Issue [ACA-Py #2209]([2209](https://github.com/hyperledger/aries-cloudagent-python/issues/2209))

✅ Supports `agent lifecycle configuration flags`

❌ multi-write ledger support is more complex. In ACA-Py, I had originally started work on this but ultimately just implemented multi-read support.

### Design
`POST /multitenancy/wallet`
- add `per_tenant_setting` dict field to the request schema. For instance:
```
{
    ...
    "ACAPY_LOG_LEVEL": "info",
    "ACAPY_INVITE_PUBLIC": True,
    "ACAPY_PUBLIC_INVITES": False,
    ...
}
```
- The `per_tenant_setting` dict will update the other wallet settings when calling the endpoint and then execute `BaseMultitenantManager -> create_wallet` to apply the per tenant + wallet settings
- `per_tenant_setting` will support the following flags as defined in the issue:
  - ACAPY_LOG_LEVEL
  - ACAPY_INVITE_PUBLIC
  - ACAPY_PUBLIC_INVITES
  - ACAPY_AUTO_ACCEPT_INVITES
  - ACAPY_AUTO_ACCEPT_REQUESTS
  - ACAPY_AUTO_PING_CONNECTION
  - ACAPY_MONITOR_PING
  - ACAPY_AUTO_RESPOND_MESSAGES
  - ACAPY_AUTO_RESPOND_CREDENTIAL_OFFER
  - ACAPY_AUTO_RESPOND_CREDENTIAL_REQUEST
  - ACAPY_AUTO_VERIFY_PRESENTATION
  - ACAPY_NOTIFY_REVOCATION
  - ACAPY_AUTO_REQUEST_ENDORSEMENT
  - ACAPY_AUTO_WRITE_TRANSACTIONS
  - ACAPY_CREATE_REVOCATION_TRANSACTIONS
- Note: `ACAPY_AUTO_REQUEST_ENDORSEMENT`, `ACAPY_AUTO_WRITE_TRANSACTIONS`, `ACAPY_CREATE_REVOCATION_TRANSACTIONS` depends upon the endorser role being an Author. To support this `per_tenant_setting` can also include `ACAPY_ENDORSER_ROLE`. If `ACAPY_ENDORSER_ROLE` is missing or if it not set as `author` then the 3 config flags will be ignored.

### Implementation
In `./aries_cloudagent/multitenant/admin/routes.py` 
1. add the following:
```
ACAPY_LIFECYCLE_CONFIG_FLAG_MAP = {
    "ACAPY_LOG_LEVEL": "log.level",
    "ACAPY_INVITE_PUBLIC": "debug.invite_public",
    "ACAPY_PUBLIC_INVITES": "public_invites",
    "ACAPY_AUTO_ACCEPT_INVITES": "debug.auto_accept_invites",
    "ACAPY_AUTO_ACCEPT_REQUESTS": "debug.auto_accept_requests",
    "ACAPY_AUTO_PING_CONNECTION": "auto_ping_connection",
    "ACAPY_MONITOR_PING": "debug.monitor_ping",
    "ACAPY_AUTO_RESPOND_MESSAGES": "debug.auto_respond_messages",
    "ACAPY_AUTO_RESPOND_CREDENTIAL_OFFER": "debug.auto_resopnd_credential_offer",
    "ACAPY_AUTO_RESPOND_CREDENTIAL_REQUEST": "debug.auto_respond_credential_request",
    "ACAPY_AUTO_VERIFY_PRESENTATION": "debug.auto_verify_presentation",
    "ACAPY_NOTIFY_REVOCATION": "revocation.notify",
    "ACAPY_AUTO_REQUEST_ENDORSEMENT": "endorser.auto_request",
    "ACAPY_AUTO_WRITE_TRANSACTIONS": "endorser.auto_write",
    "ACAPY_CREATE_REVOCATION_TRANSACTIONS": "endorser.auto_create_rev_reg",
    "ACAPY_ENDORSER_ROLE": "endorser.protocol_role",
}

ACAPY_ENDORSER_FLGAS_DEPENDENT_ON_AUTHOR_ROLE = [
    "ACAPY_AUTO_REQUEST_ENDORSEMENT",
    "ACAPY_AUTO_WRITE_TRANSACTIONS",
    "ACAPY_CREATE_REVOCATION_TRANSACTIONS",
]

def get_extra_settings_dict_per_tenant(tenant_settings: dict) -> dict:
    """Get per tenant settings to be applied when creating wallet."""

    endorser_role_flag = tenant_settings.get("ACAPY_ENDORSER_ROLE")
    extra_settings = {}
    if endorser_role_flag == "author":
        extra_settings["endorser.author"] = True
    elif endorser_role_flag == "endorser":
        extra_settings["endorser.endorser"] = True
    for flag in tenant_settings.keys():
        if flag in ACAPY_ENDORSER_FLGAS_DEPENDENT_ON_AUTHOR_ROLE and endorser_role_flag != "author":
            # These flags require endorser role as author, if not set as author then
            # this setting will be ignored.
            continue
        if flag != "ACAPY_ENDORSER_ROLE":
            map_flag = ACAPY_LIFECYCLE_CONFIG_FLAG_MAP[flag]
            extra_settings[map_flag] = tenant_settings[flag]
    return extra_settings
```

2. update `CreateWalletRequestSchema(OpenAPISchema)` and add the following field:
```
per_tenant_setting = fields.Dict(
    keys=fields.Str(description="Agent Config Flag"),
    values=fields.Str(description="Parameter"),
    allow_none=True,
)
```

3. update `wallet_create()` [snippet]:
```
per_tenant_setting = body.get("per_tenant_setting") or {}
# If no webhooks specified, then dispatch only to base webhook targets
if wallet_webhook_urls == []:
    wallet_dispatch_type = "base"

settings = {
    "wallet.type": body.get("wallet_type") or "in_memory",
    "wallet.name": body.get("wallet_name"),
    "wallet.key": wallet_key,
    "wallet.webhook_urls": wallet_webhook_urls,
    "wallet.dispatch_type": wallet_dispatch_type,
}
extra_tenant_setting = get_extra_settings_dict_per_tenant(per_tenant_setting)
settings.update(extra_tenant_setting)
```

