Skip to content

Commit

Permalink
Merge pull request #1294 from bcgov/hard-tenant-deletion
Browse files Browse the repository at this point in the history
Support fully delete/drop tenant and associated wallet
  • Loading branch information
Gavinok committed Aug 2, 2024
2 parents af400a5 + 36e161c commit 9832074
Show file tree
Hide file tree
Showing 10 changed files with 253 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -809,7 +809,35 @@ async def innkeeper_tenant_delete(request: web.BaseRequest):
return web.json_response({"success": f"Tenant {tenant_id} soft deleted."})
else:
raise web.HTTPNotFound(reason=f"Tenant {tenant_id} not found.")



@docs(
tags=[SWAGGER_CATEGORY],
)
@match_info_schema(TenantIdMatchInfoSchema())
@response_schema(TenantRecordSchema(), 200, description="")
@innkeeper_only
@error_handler
async def innkeeper_tenant_hard_delete(request: web.BaseRequest):
context: AdminRequestContext = request["context"]
tenant_id = request.match_info["tenant_id"]

mgr = context.inject(TenantManager)
profile = mgr.profile
async with profile.session() as session:
rec = await TenantRecord.retrieve_by_id(session, tenant_id)
if rec:
multitenant_mgr = context.profile.inject(BaseMultitenantManager)
wallet_id = rec.wallet_id
wallet_record = await WalletRecord.retrieve_by_id(session, wallet_id)

await multitenant_mgr.remove_wallet(wallet_id)
await rec.delete_record(session)
LOGGER.info("Tenant %s rd deleted.", tenant_id)
return web.json_response({"success": f"Tenant {tenant_id} hard deleted."})
else:
raise web.HTTPNotFound(reason=f"Tenant {tenant_id} not found.")


@docs(
tags=[SWAGGER_CATEGORY],
Expand All @@ -822,10 +850,10 @@ async def innkeeper_tenant_delete(request: web.BaseRequest):
async def innkeeper_tenant_restore(request: web.BaseRequest):
context: AdminRequestContext = request["context"]
tenant_id = request.match_info["tenant_id"]

mgr = context.inject(TenantManager)
profile = mgr.profile

async with profile.session() as session:
rec = await TenantRecord.retrieve_by_id(session, tenant_id)
if rec:
Expand All @@ -837,7 +865,7 @@ async def innkeeper_tenant_restore(request: web.BaseRequest):
return web.json_response({"success": f"Tenant {tenant_id} restored."})
else:
raise web.HTTPNotFound(reason=f"Tenant {tenant_id} not found.")


@docs(tags=[SWAGGER_CATEGORY], summary="Create API Key Record")
@request_schema(TenantAuthenticationsApiRequestSchema())
Expand Down Expand Up @@ -960,9 +988,7 @@ async def innkeeper_config_handler(request: web.BaseRequest):
profile = mgr.profile

config = {
key: (
profile.context.settings[key]
)
key: (profile.context.settings[key])
for key in profile.context.settings
if key
not in [
Expand All @@ -975,9 +1001,13 @@ async def innkeeper_config_handler(request: web.BaseRequest):
]
}
try:
del config["plugin_config"]["traction_innkeeper"]["innkeeper_wallet"]["wallet_key"]
del config["plugin_config"]["traction_innkeeper"]["innkeeper_wallet"][
"wallet_key"
]
except KeyError as e:
LOGGER.warn(f"The key to be removed: '{e.args[0]}' is missing from the dictionary.")
LOGGER.warn(
f"The key to be removed: '{e.args[0]}' is missing from the dictionary."
)
config["version"] = __version__

return web.json_response({"config": config})
Expand Down Expand Up @@ -1033,6 +1063,9 @@ async def register(app: web.Application):
),
web.put("/innkeeper/tenants/{tenant_id}/config", tenant_config_update),
web.delete("/innkeeper/tenants/{tenant_id}", innkeeper_tenant_delete),
web.delete(
"/innkeeper/tenants/{tenant_id}/hard", innkeeper_tenant_hard_delete
),
web.put("/innkeeper/tenants/{tenant_id}/restore", innkeeper_tenant_restore),
web.get(
"/innkeeper/default-config",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ class TenantLedgerIdConfigSchema(OpenAPISchema):
required=True,
)


@web.middleware
async def setup_tenant_context(request: web.Request, handler):
"""Middle ware to extract tenant_id and provide it to log formatter
Expand Down Expand Up @@ -465,6 +466,55 @@ async def tenant_server_config_handler(request: web.BaseRequest):
return web.json_response({"config": config})


@docs(
tags=[SWAGGER_CATEGORY],
)
@response_schema(TenantRecordSchema(), 200, description="")
@error_handler
async def tenant_delete_soft(request: web.BaseRequest):
context: AdminRequestContext = request["context"]
wallet_id = context.profile.settings.get("wallet.id")

mgr = context.inject(TenantManager)
profile = mgr.profile
async with profile.session() as session:
rec = await TenantRecord.query_by_wallet_id(session, wallet_id)
if rec:
await rec.soft_delete(session)
LOGGER.info("Tenant %s soft deleted.", rec.tenant_id)
return web.json_response(
{"success": f"Tenant {rec.tenant_id} soft deleted."}
)
else:
raise web.HTTPNotFound(reason=f"Tenant not found.")


@docs(
tags=[SWAGGER_CATEGORY],
)
@response_schema(TenantRecordSchema(), 200, description="")
@error_handler
async def tenant_delete(request: web.BaseRequest):
context: AdminRequestContext = request["context"]
wallet_id = context.profile.settings.get("wallet.id")

mgr = context.inject(TenantManager)
profile = mgr.profile
async with profile.session() as session:
rec = await TenantRecord.query_by_wallet_id(session, wallet_id)
if rec:
multitenant_mgr = context.profile.inject(BaseMultitenantManager)

await multitenant_mgr.remove_wallet(rec.wallet_id)
await rec.delete_record(session)
LOGGER.info("Tenant %s rd deleted.", rec.tenant_id)
return web.json_response(rec.serialize())
else:
raise web.HTTPNotFound(
reason=f"Tenant with wallet id {wallet_id} not found."
)


async def register(app: web.Application):
"""Register routes."""
LOGGER.info("> registering routes")
Expand Down Expand Up @@ -499,6 +549,8 @@ async def register(app: web.Application):
tenant_server_config_handler,
allow_head=False,
),
web.delete("/tenant/hard", tenant_delete),
web.delete("/tenant/soft", tenant_delete_soft),
]
)
LOGGER.info("< registering routes")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

button {
&.p-button {
&:not(.p-button-rounded):not(.p-button-text):not(.p-button-link) {
&:not(.p-button-rounded):not(.p-danger):not(.p-button-text):not(.p-button-link) {
padding: 0.5rem 1rem;
background-color: $tenant-ui-secondary-color;
border-color: $tenant-ui-secondary-color;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,32 +14,59 @@
placeholder="Type the tenant name here"
class="w-full mb-4"
/>
<div>
<div class="flex items-center">
<RadioButton v-model="perminant" value="Hard" />
<label class="ml-2" for="">
{{ $t('tenants.settings.permanentDelete') }}
</label>
</div>
<div class="flex items-center my-2">
<RadioButton v-model="perminant" value="Soft" />
<label class="ml-2" for="">
{{ $t('tenants.settings.softDelete') }}
</label>
</div>
</div>
<div v-if="displayWarning" class="ml-2 my-2 flex flex-row">
<i class="pi pi-info-circle mr-2"></i>
<div class="text-yellow-500 flex flex-row font-bold">
<div class="font-bold">{{ $t('common.warning') }}</div>
<div class="font-semibold">
{{ $t('tenants.settings.tenantDeletionWarning') }}
</div>
</div>
</div>
<Button
:disabled="!isTenantNameCorrect"
label="Delete"
severity="danger"
class="w-full"
class="w-full my-2"
type="submit"
/>
</div>
</form>
</template>

<script setup lang="ts">
import { ref, computed } from 'vue';
import { ref, computed, Ref } from 'vue';
import InputText from 'primevue/inputtext';
import RadioButton from 'primevue/radiobutton';
import Button from 'primevue/button';
import { useInnkeeperTenantsStore } from '@/store';
import { TenantRecord } from '@/types/acapyApi/acapyInterface';
import { useTenantStore } from '@/store';
import { useToast } from 'vue-toastification';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
type DeletionAPI = 'Innkeeper' | 'Tenant';
const props = defineProps<{
tenant: TenantRecord;
api: DeletionAPI;
}>();
const emit = defineEmits(['closed', 'success']);
Expand All @@ -51,16 +78,46 @@ const confirmationTenantName = ref('');
const isTenantNameCorrect = computed(
() => confirmationTenantName.value === props.tenant.tenant_name
);
const displayWarning = computed(() => {
if (perminant.value == 'Hard') return true;
else return false;
});
// toast.info("hello");
const toast = useToast();
type DeletionType = 'Hard' | 'Soft';
const perminant: Ref<DeletionType> = ref('Soft');
const tenantDelete = async () => {
const tenantStore = useTenantStore();
if (perminant.value == 'Hard') {
tenantStore.deleteTenant();
window.location.href = '/logout';
} else {
tenantStore.softDeleteTenant();
window.location.href = '/logout';
}
};
const innkeeperDelete = async () => {
if (perminant.value == 'Hard') {
await innkeeperTenantsStore.hardDeleteTenant(props.tenant.tenant_id);
} else {
await innkeeperTenantsStore.deleteTenant(props.tenant.tenant_id);
}
};
async function handleDelete() {
if (!isTenantNameCorrect.value) {
toast.error(t('tenants.settings.confirmDeletionIncorrect'));
return;
}
try {
await innkeeperTenantsStore.deleteTenant(props.tenant.tenant_id);
if (props.api == 'Tenant') {
await tenantDelete();
} else {
await innkeeperDelete();
}
toast.success(
t('tenants.settings.confirmDeletionSuccess', [props.tenant.tenant_name])
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
>
<ConfirmTenantDeletion
:tenant="props.tenant"
api="Innkeeper"
@success="$emit('success')"
@closed="handleClose"
/>
Expand Down
3 changes: 3 additions & 0 deletions services/tenant-ui/frontend/src/helpers/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export const API_PATH = {
INNKEEPER_TOKEN: '/innkeeper/token',
INNKEEPER_TENANTS: '/innkeeper/tenants/',
INNKEEPER_TENANT: (id: string) => `/innkeeper/tenants/${id}`,
INNKEEPER_HARD_DELETE_TENANT: (id: string) => `/innkeeper/tenants/${id}/hard`,
INNKEEPER_TENANT_CONFIG: (id: string) => `/innkeeper/tenants/${id}/config`,
INNKEEPER_TENANT_DEFAULT_CONFIG: '/innkeeper/default-config',
INNKEEPER_TENANT_RESTORE: (id: string) => `/innkeeper/tenants/${id}/restore`,
Expand Down Expand Up @@ -143,6 +144,8 @@ export const API_PATH = {
SCHEMA_STORAGE_ITEM: (id: string) => `/schema-storage/${id}`,

TENANT_SELF: '/tenant',
TENANT_DELETE: '/tenant/hard',
TENANT_DELETE_SOFT: '/tenant/soft',
TENANT_ENDORSER_CONNECTION: '/tenant/endorser-connection',
TENANT_ENDORSER_INFO: '/tenant/endorser-info',
TENANT_REGISTER_PUBLIC_DID: '/ledger/register-nym',
Expand Down
8 changes: 6 additions & 2 deletions services/tenant-ui/frontend/src/plugins/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@
"tenantName": "Tenant Name",
"tenantReason": "Tenant Reason",
"toggleCardExpand": "Toggle Expanded",
"walletId": "Wallet ID"
"walletId": "Wallet ID",
"warning": "Warning"
},
"configuration": {
"configuration": "Configuration",
Expand Down Expand Up @@ -524,6 +525,9 @@
"confirmDeletionIncorrect": "Incorrect tenant name. Please confirm the correct name before deletion.",
"confirmDeletionSuccess": "Tenant {0} successfully marked as deleted",
"deleteTenant": "Delete Tenant",
"permanentDelete": "Permanently Delete",
"softDelete": "Suspend Tenant",
"tenantDeletionWarning": ": This will delete all data associated with this tenant.",
"editSettings": "Edit Tenant Config",
"enableLedgerSwitch": "Tenant can switch endorser/ledger",
"endorserAlias": "Endorser Alias:",
Expand Down Expand Up @@ -561,4 +565,4 @@
"verification": "Verification",
"verifications": "Verifications"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,21 @@ export const useInnkeeperTenantsStore = defineStore('innkeeperTenants', () => {
}
}

async function hardDeleteTenant(id: string) {
loading.value = true;
try {
await acapyApi.deleteHttp(API_PATH.INNKEEPER_HARD_DELETE_TENANT(id));
await listTenants();
} catch (err: any) {
error.value = err;
} finally {
loading.value = false;
}
if (error.value != null) {
throw error.value;
}
}

// Create an API key for a tenant
async function createApiKey(payload: TenantAuthenticationsApiRequest) {
error.value = null;
Expand Down Expand Up @@ -423,6 +438,7 @@ export const useInnkeeperTenantsStore = defineStore('innkeeperTenants', () => {
listTenants,
refreshCheckInPassword,
restoreTenant,
hardDeleteTenant,
updateTenantConfig,
};
});
Expand Down
Loading

0 comments on commit 9832074

Please sign in to comment.