Skip to content

Commit

Permalink
feat(condo): DOMA-6932 support propertyQuery for check file access (#…
Browse files Browse the repository at this point in the history
…3749)

* fix(condo): DOMA-6932 fix sensitiveFile ACL meta to make it accessible

* fix(condo): DOMA-6932 support propertyQuery for check file access

* fix(condo): DOMA-6932 support propertyQuery for check file access

* fix(condo): DOMA-6932 support propertyQuery for check file access

* fix(condo): DOMA-6932 support propertyQuery for check file access

* fix(condo): DOMA-6932 support propertyQuery for check file access

* fix(condo): DOMA-6932 support propertyQuery for check file access

* fix(condo): DOMA-6932 support propertyQuery for check file access
  • Loading branch information
ekabardinsky authored Aug 24, 2023
1 parent 972a5db commit 6e86fd6
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 18 deletions.
14 changes: 12 additions & 2 deletions apps/condo/domains/billing/schema/BillingReceiptFile.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/

const { Text, Relationship, File, Virtual } = require('@keystonejs/fields')
const get = require('lodash/get')
const { get, isNil } = require('lodash')

const { historical, versioned, uuided, tracked, softDeleted, dvAndSender } = require('@open-condo/keystone/plugins')
const { GQLListSchema, getById, find } = require('@open-condo/keystone/schema')
Expand Down Expand Up @@ -38,6 +38,11 @@ const BillingReceiptFile = new GQLListSchema('BillingReceiptFile', {
type: Virtual,
graphQLReturnType: 'File',
resolver: async (item, _, { authedItem }) => {
// no authed item filled up case
if (isNil(authedItem)) {
return
}

// We are changing link to publicData only for not verified residents. In other cases we return sensitive data files
let file = item.publicDataFile
if (authedItem.type === RESIDENT) {
Expand Down Expand Up @@ -123,8 +128,13 @@ const BillingReceiptFile = new GQLListSchema('BillingReceiptFile', {
const sensitiveFile = get(updatedItem, 'sensitiveDataFile.filename')
const publicFile = get(updatedItem, 'publicDataFile.filename')
const key = (filename) => `${BILLING_RECEIPT_FILE_FOLDER_NAME}/${filename}`

// set files ACL meta
if (sensitiveFile) {
await Adapter.acl.setMeta(key(sensitiveFile), { listkey: 'BillingIntegrationOrganizationContext', id: updatedItem.context })
await Adapter.acl.setMeta(key(sensitiveFile), {
listkey: listKey, id: updatedItem.id,
propertyquery: 'file { filename }', propertyvalue: sensitiveFile,
})
}
if (publicFile) {
await Adapter.acl.setMeta(key(publicFile), { listkey: listKey, id: updatedItem.id })
Expand Down
37 changes: 33 additions & 4 deletions apps/condo/domains/common/utils/sberCloudFileAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const path = require('path')
const { getItem, getItems } = require('@keystonejs/server-side-graphql-client')
const ObsClient = require('esdk-obs-nodejs')
const express = require('express')
const { isEmpty, isString } = require('lodash')
const { get, isEmpty, isString, isNil } = require('lodash')

const { SERVER_URL, SBERCLOUD_OBS_CONFIG } = require('@open-condo/config')

Expand Down Expand Up @@ -187,7 +187,15 @@ const obsRouterHandler = ({ keystone }) => {
res.status(404)
return next()
}
const { id: itemId, ids: stringItemIds, listkey: listKey } = meta
const {
id: itemId,
ids: stringItemIds,
listkey: listKey,
propertyquery: encodedPropertyQuery,
propertyvalue: encodedPropertyValue,
} = meta
const propertyQuery = !isNil(encodedPropertyQuery) ? decodeURI(encodedPropertyQuery) : null
const propertyValue = !isNil(encodedPropertyValue) ? decodeURI(encodedPropertyValue) : null

if ((isEmpty(itemId) && isEmpty(stringItemIds)) || isEmpty(listKey)) {
res.status(404)
Expand All @@ -214,13 +222,33 @@ const obsRouterHandler = ({ keystone }) => {
}

if (itemId && !hasAccessToReadFile) {
hasAccessToReadFile = await getItem({
let returnFields = 'id'

// for checking property we have to include property name in the return fields list
if (isString(propertyQuery)) {
returnFields += `, ${propertyQuery}`
}

const item = await getItem({
keystone,
listKey,
itemId,
context,
returnFields: 'id',
returnFields,
})

// item accessible
hasAccessToReadFile = !isNil(item)

// check property access case
if (hasAccessToReadFile && isString(propertyQuery) && !isNil(propertyValue)) {
const propertyPath = propertyQuery
.replaceAll('}', '') // remove close brackets of sub props querying
.split('{') // work with each path parts separately
.map(path => path.trim()) // since gql allow to have spaces in querying - let's remove them
.join('.') // join by . for lodash get utility
hasAccessToReadFile = get(item, propertyPath) == propertyValue
}
}

if (!hasAccessToReadFile) {
Expand Down Expand Up @@ -264,4 +292,5 @@ class OBSFilesMiddleware {
module.exports = {
SberCloudFileAdapter,
OBSFilesMiddleware,
obsRouterHandler,
}
133 changes: 121 additions & 12 deletions apps/condo/domains/common/utils/sberCloudFileAdapter.spec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,29 @@
/**
* @jest-environment node
*/
const index = require('@app/condo/index')
const ObsClient = require('esdk-obs-nodejs')

const {
setFakeClientMode,
makeLoggedInAdminClient,
} = require('@open-condo/keystone/test.utils')

const {
createTestBillingIntegrationOrganizationContext,
} = require('@condo/domains/billing/utils/testSchema')
const {
createTestBillingIntegration,
} = require('@condo/domains/billing/utils/testSchema')
const { makeClientWithProperty } = require('@condo/domains/property/utils/testSchema')
const {
makeClientWithSupportUser,
} = require('@condo/domains/user/utils/testSchema')

const { obsRouterHandler } = require('./sberCloudFileAdapter')

const { keystone } = index

const FOLDER_NAME = '__jest_test_api___'


Expand All @@ -9,7 +34,7 @@ class SberCloudObsTest {
throw new Error('SberCloudAdapter: S3Adapter requires a bucket name.')
}
this.obs = new ObsClient(config.s3Options)
this.folder = config.folder
this.folder = config.folder
}

async checkBucket () {
Expand Down Expand Up @@ -44,10 +69,10 @@ class SberCloudObsTest {
return serverAnswer
}

async getMeta (filename) {
const result = await this.s3.getObjectMetadata({
async getMeta (name) {
const result = await this.obs.getObjectMetadata({
Bucket: this.bucket,
Key: filename,
Key: `${this.folder}/${name}`,
})
if (result.CommonMsg.Status < 300) {
return result.InterfaceResult.Metadata
Expand All @@ -56,17 +81,17 @@ class SberCloudObsTest {
}
}

async setMeta (filename, newMeta = {} ) {
const result = await this.s3.setObjectMetadata({
async setMeta (name, newMeta = {}) {
const result = await this.obs.setObjectMetadata({
Bucket: this.bucket,
Key: filename,
Key: `${this.folder}/${name}`,
Metadata: newMeta,
MetadataDirective: 'REPLACE_NEW',
})
const { CommonMsg: { Status } } = result
const { CommonMsg: { Status } } = result
return Status < 300
}
}

static async initApi () {
const S3Config = {
...(process.env.SBERCLOUD_OBS_CONFIG ? JSON.parse(process.env.SBERCLOUD_OBS_CONFIG) : {}),
Expand All @@ -83,11 +108,35 @@ class SberCloudObsTest {
return null
}
return Api
}
}
}


describe('Sbercloud', () => {
let handler
let mockedNext, mockedReq, mockedRes

setFakeClientMode(index)

beforeAll(async () => {
handler = obsRouterHandler({ keystone })
mockedNext = () => {
throw new Error('calling method not expected by test cases')
}
mockedReq = (file, user) => ({
get: (header) => header,
params: { file },
user,
})
mockedRes = {
sendStatus: mockedNext,
status: mockedNext,
end: mockedNext,
json: mockedNext,
redirect: mockedNext,
}
})

describe('Huawei SDK', () => {
it('can add file to s3', async () => {
const Api = await SberCloudObsTest.initApi()
Expand Down Expand Up @@ -125,6 +174,66 @@ describe('Sbercloud', () => {
const { CommonMsg: { Status: checkStatus } } = await Api.checkObjectExists(name)
expect(checkStatus).toBe(404)
}
})
})
})
describe('Check access to read file', () => {
let userClient, support, adminClient,
integration, billingContext, Api,
getFileWithMeta

beforeAll(async () => {
Api = await SberCloudObsTest.initApi()
if (Api) {
userClient = await makeClientWithProperty()
support = await makeClientWithSupportUser()
adminClient = await makeLoggedInAdminClient()

integration = (await createTestBillingIntegration(support))[0]
billingContext = (await createTestBillingIntegrationOrganizationContext(userClient, userClient.organization, integration))[0]
}

getFileWithMeta = async (meta) => {
const name = `testFile_${Math.random()}.txt` // NOSONAR
const objectName = `${FOLDER_NAME}/${name}`
await Api.uploadObject(name, `Random text ${Math.random()}`) // NOSONAR
const setMetaResult = await Api.setMeta(name, meta)
expect(setMetaResult).toBe(true)

return {
name, objectName,
}
}
})

it('check access for read file by model', async () => {
if (Api) {
const { objectName } = await getFileWithMeta({
listkey: 'BillingIntegrationOrganizationContext',
id: billingContext.id,
})

handler(
mockedReq(objectName, adminClient.user),
{ ...mockedRes, redirect: console.log },
mockedNext,
)
}
})
it('check access for read file by model param', async () => {
if (Api) {
const { objectName } = await getFileWithMeta({
listkey: 'BillingIntegrationOrganizationContext',
id: billingContext.id,
propertyquery: 'organization { id }',
propertyvalue: userClient.organization.id,
})

handler(
mockedReq(objectName, adminClient.user),
{ ...mockedRes, redirect: console.log },
mockedNext,
)
}
})
})
})

0 comments on commit 6e86fd6

Please sign in to comment.