Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
637 changes: 295 additions & 342 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@gen3/guppy",
"version": "0.3.14",
"version": "0.3.15",
"description": "Server that support GraphQL queries on data from elasticsearch",
"main": "src/server/server.js",
"directories": {
Expand Down Expand Up @@ -28,7 +28,7 @@
"homepage": "https://github.com/uc-cdis/guppy#readme",
"dependencies": {
"@elastic/elasticsearch": "^7.0.0-rc.1",
"@gen3/ui-component": "^0.3.15",
"@gen3/ui-component": "^0.3.16",
"apollo-server": "^2.4.8",
"apollo-server-express": "^2.4.8",
"array.prototype.flat": "^1.2.2",
Expand Down
6 changes: 5 additions & 1 deletion src/components/ConnectedFilter/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,11 @@ class ConnectedFilter extends React.Component {
}

processedTabsOptions = updateCountsInInitialTabsOptions(
this.initialTabsOptions, processedTabsOptions, this.state.filtersApplied,
this.initialTabsOptions,
processedTabsOptions,
this.state.filtersApplied,
// for tiered access filters
this.props.tierAccessLimit ? this.props.accessibleFieldCheckList : [],
);
processedTabsOptions = sortTabsOptions(processedTabsOptions);

Expand Down
1 change: 1 addition & 0 deletions src/components/GuppyWrapper/index.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable react/jsx-fragments */
/* eslint react/forbid-prop-types: 0 */
import React from 'react';
import PropTypes from 'prop-types';
Expand Down
10 changes: 9 additions & 1 deletion src/components/Utils/filters.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,20 @@ export const mergeFilters = (userFilter, adminAppliedPreFilter) => {
* they are still checked but their counts are zero.
*/
export const updateCountsInInitialTabsOptions = (
initialTabsOptions, processedTabsOptions, filtersApplied,
initialTabsOptions, processedTabsOptions, filtersApplied, accessibleFieldCheckList,
) => {
const updatedTabsOptions = {};
try {
Object.keys(initialTabsOptions).forEach((field) => {
updatedTabsOptions[field] = { histogram: [] };
// if in tiered access mode
// we need not to process filters for field in accessibleFieldCheckList
if (accessibleFieldCheckList
&& accessibleFieldCheckList.includes(field)
&& processedTabsOptions[field]) {
updatedTabsOptions[field] = processedTabsOptions[field];
return;
}
const { histogram } = initialTabsOptions[field];
histogram.forEach((opt) => {
const { key } = opt;
Expand Down
11 changes: 6 additions & 5 deletions src/server/auth/authHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ export class AuthHelper {
async initialize() {
try {
this._accessibleResourceList = await getAccessibleResourcesFromArboristasync(this._jwt);
const promistList = [];
const promiseList = [];
config.esConfig.indices.forEach(({ index, type }) => {
const subListPromise = this.getOutOfScopeResourceList(index, type);
promistList.push(subListPromise);
promiseList.push(subListPromise);
});
this._unaccessibleResourceList = [];
const listResult = await Promise.all(promistList);
const listResult = await Promise.all(promiseList);
listResult.forEach((list) => {
this._unaccessibleResourceList = _.union(this._unaccessibleResourceList, list);
});
Expand All @@ -41,10 +41,11 @@ export class AuthHelper {
return this._unaccessibleResourceList;
}

async getOutOfScopeResourceList(esIndex, esType, filter) {
async getOutOfScopeResourceList(esIndex, esType, filter, filterSelf) {
const requestResourceList = await getRequestResourceListFromFilter(
esIndex, esType, filter,
esIndex, esType, filter, filterSelf,
);
log.debug('[AuthHelper] filter:', filter);
log.debug(`[AuthHelper] request resource list: [${requestResourceList.join(', ')}]`);
const outOfScopeResourceList = _.difference(requestResourceList, this._accessibleResourceList);
log.debug(`[AuthHelper] out-of-scope resource list: [${outOfScopeResourceList.join(', ')}]`);
Expand Down
6 changes: 4 additions & 2 deletions src/server/auth/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ export const getAccessibleResourcesFromArboristasync = async (jwt) => {
return resources;
};

export const getRequestResourceListFromFilter = async (esIndex, esType, filter) => textAggregation(
export const getRequestResourceListFromFilter = async (
esIndex, esType, filter, filterSelf,
) => textAggregation(
{ esInstance, esIndex, esType },
{ field: config.esConfig.authFilterField, filter },
{ field: config.esConfig.authFilterField, filter, filterSelf },
).then((res) => (res.map((item) => item.key)));

export const buildFilterWithResourceList = (resourceList = []) => {
Expand Down
2 changes: 1 addition & 1 deletion src/server/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const config = {
tierAccessLevel: 'private',
tierAccessLimit: 1000,
tierAccessSensitiveRecordExclusionField: inputConfig.tier_access_sensitive_record_exclusion_field,
logLevel: 'INFO',
logLevel: inputConfig.log_level || 'INFO',
enableEncryptWhiteList: typeof inputConfig.enable_encrypt_whitelist === 'undefined' ? false : inputConfig.enable_encrypt_whitelist,
encryptWhitelist: inputConfig.encrypt_whitelist || ['__missing__', 'unknown', 'not reported', 'no data'],
analyzedTextFieldSuffix: '.analyzed',
Expand Down
39 changes: 29 additions & 10 deletions src/server/middlewares/tierAccessMiddleware/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import _ from 'lodash';
import assert from 'assert';
import { ApolloError, UserInputError } from 'apollo-server';
import log from '../../logger';
Expand Down Expand Up @@ -42,20 +43,24 @@ const tierAccessResolver = (
assert(config.tierAccessLevel === 'regular', 'Tier access middleware layer only for "regular" tier access level');
const { authHelper } = context;
const esIndex = esInstance.getESIndexByType(esType);
const { filter, accessibility } = args;
const { filter, filterSelf, accessibility } = args;

const outOfScopeResourceList = await authHelper.getOutOfScopeResourceList(
esIndex, esType, filter,
esIndex, esType, filter, filterSelf,
);
// if requesting resources is within allowed resources, return result
if (outOfScopeResourceList.length === 0) {
// unless it's requesting for `unaccessible` data, just resolve this
if (accessibility !== 'unaccessible') {
return resolve(root, { ...args, needEncryptAgg: false }, context, info);
switch (accessibility) {
case 'accessible':
return resolve(root, { ...args, needEncryptAgg: false }, context, info);
case 'unaccessible':
return resolverWithUnaccessibleFilterApplied(
resolve, root, args, context, info, authHelper, filter,
);
default:
return resolve(root, { ...args, needEncryptAgg: true }, context, info);
}
return resolverWithUnaccessibleFilterApplied(
resolve, root, args, context, info, authHelper, filter,
);
}
// else, check if it's raw data query or aggs query
if (isRawDataQuery) { // raw data query for out-of-scope resources are forbidden
Expand Down Expand Up @@ -175,16 +180,30 @@ const hideNumberResolver = (isGettingTotalCount) => async (resolve, root, args,
// for aggregations, hide all counts that are greater than limited number
const { needEncryptAgg } = root;
const result = await resolve(root, args, context, info);
log.debug('[hideNumberResolver] result: ', result);
if (!needEncryptAgg) return result;

const newRoot = root;
newRoot.accessibility = 'unaccessible';
const { authHelper } = context;
newRoot.filter = authHelper.applyUnaccessibleFilter(newRoot.filter);
const unaccessibleResult = await resolve(newRoot, args, context, info);
log.debug('[hideNumberResolver] unaccessibleResult: ', unaccessibleResult);

// if getting total count, only encrypt if unaccessibleResult is between (0, tierAccessLimit)
if (isGettingTotalCount) {
return (result < config.tierAccessLimit) ? ENCRYPT_COUNT : result;
return (unaccessibleResult > 0
&& unaccessibleResult < config.tierAccessLimit) ? ENCRYPT_COUNT : result;
}

const encryptedResult = result.map((item) => {
if (isWhitelisted(item.key)) { // we don't encrypt whitelisted results
// we don't encrypt whitelisted results or if result is not found in unaccessibleResult
if (isWhitelisted(item.key) || !(unaccessibleResult.some((e) => e.key === item.key))) {
return item;
}
if (item.count < config.tierAccessLimit) {
// we only encrypt if count from no-access item is small
const unaccessibleResultItem = _.find(unaccessibleResult, (e) => e.key === item.key);
if (unaccessibleResultItem.count < config.tierAccessLimit) {
return {
key: item.key,
count: ENCRYPT_COUNT,
Expand Down
6 changes: 3 additions & 3 deletions stories/connectedFilter.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import './guppyWrapper.css';

storiesOf('ConnectedFilter', module)
.add('Filter', () => {
const processFilterAggsData = aggsData => aggsData;
const processFilterAggsData = (aggsData) => aggsData;
return (
<ConnectedFilter
filterConfig={filterConfig}
Expand All @@ -22,7 +22,7 @@ storiesOf('ConnectedFilter', module)
);
})
.add('Accessible Filter', () => {
const processFilterAggsData = aggsData => aggsData;
const processFilterAggsData = (aggsData) => aggsData;
return (
<AccessibleFilter
filterConfig={filterConfig}
Expand All @@ -34,7 +34,7 @@ storiesOf('ConnectedFilter', module)
);
})
.add('Unaccessible Filter', () => {
const processFilterAggsData = aggsData => aggsData;
const processFilterAggsData = (aggsData) => aggsData;
return (
<UnaccessibleFilter
filterConfig={filterConfig}
Expand Down