Skip to content

Commit c89aff7

Browse files
matthewpeterkortdependabot[bot]mfshaoAlbertSnowsBinamB
authored
Feature/refresh (#273)
* Adds initial support for guppy refresh * Fix Formatting * Adds create method checking, updates tests * fix: test * Addresses PR feedback * chore(deps): bump braces from 3.0.2 to 3.0.3 (#271) * chore(deps): bump braces from 3.0.2 to 3.0.3 Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3. - [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md) - [Commits](micromatch/braces@3.0.2...3.0.3) --- updated-dependencies: - dependency-name: braces dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> * trigger gh action * trigger travis --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Mingfei Shao <mshao1@uchicago.edu> * chore(deps-dev): bump ws from 6.2.2 to 6.2.3 (#272) Bumps [ws](https://github.com/websockets/ws) from 6.2.2 to 6.2.3. - [Release notes](https://github.com/websockets/ws/releases) - [Commits](websockets/ws@6.2.2...6.2.3) --- updated-dependencies: - dependency-name: ws dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Mingfei Shao <2475897+mfshao@users.noreply.github.com> * address recent feedback * fix apply hide number resolver to as text agg results (#274) * fix apply hide number resolver to as text agg results * update dockerfile * Fix param ordering in server.js (#277) Param order was wrong on changed line, put err first. Refer to: https://expressjs.com/en/guide/error-handling.html * fix: package.json & package-lock.json to reduce vulnerabilities (#280) The following vulnerabilities are fixed with an upgrade: - https://snyk.io/vuln/SNYK-JS-SEND-7926862 Co-authored-by: snyk-bot <snyk-bot@snyk.io> * Update integration_tests.yaml (#284) * (PPS-1564 PPS-1567 PPS-1585): update body-parser and package lock file to use newer dependencies (#285) * PPS-1564 PPS-1567 PPS-1585: update body-parser and package lock file to use newer dependencies * PPS-1564 PPS-1567 PPS-1585: add file so nvm can set node version * Update quickstart_helm.md (#289) Fix a dead link * Adds initial support for guppy refresh * run linter * bug fixes * fix es-lint errors --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Mingfei Shao <mshao1@uchicago.edu> Co-authored-by: Mingfei Shao <2475897+mfshao@users.noreply.github.com> Co-authored-by: Albert Snow <ajsnow2012@gmail.com> Co-authored-by: Binam Bajracharya <44302895+BinamB@users.noreply.github.com> Co-authored-by: snyk-bot <snyk-bot@snyk.io> Co-authored-by: Krishna Agarwal <159047652+krishnaa05@users.noreply.github.com> Co-authored-by: Shawn O'Connor <ocshawn@ocshawn.com> Co-authored-by: smvgarcia <111767892+smvgarcia@users.noreply.github.com>
1 parent b6bbc9a commit c89aff7

File tree

10 files changed

+228
-116
lines changed

10 files changed

+228
-116
lines changed

src/server/__mocks__/mockDataFromES.js

+36-32
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,19 @@ import mockNestedTermsAndMissingAggs from './mockESData/mockNestedTermsAndMissin
88
import mockNestedAggs from './mockESData/mockNestedAggs';
99

1010
const mockPing = () => {
11-
nock(config.esConfig.host)
12-
.head('/')
13-
.reply(200, 'hello');
11+
nock(config.esConfig.host).head('/').reply(200, 'hello');
12+
};
13+
14+
const mockRefresh = () => {
15+
if (config.allowRefresh) {
16+
nock(config.esConfig.host)
17+
.post('/_refresh')
18+
.reply(200, '[Server] guppy refreshed successfully');
19+
} else {
20+
nock(config.esConfig.host)
21+
.post('/_refresh')
22+
.reply(404, '[Server] guppy _refresh functionality is not enabled');
23+
}
1424
};
1525

1626
const mockResourcePath = () => {
@@ -35,12 +45,8 @@ const mockResourcePath = () => {
3545
},
3646
},
3747
highlight: {
38-
pre_tags: [
39-
'<em>',
40-
],
41-
post_tags: [
42-
'</em>',
43-
],
48+
pre_tags: ['<em>'],
49+
post_tags: ['</em>'],
4450
fields: {
4551
'*.analyzed': {},
4652
},
@@ -111,12 +117,8 @@ const mockResourcePath = () => {
111117
},
112118
},
113119
highlight: {
114-
pre_tags: [
115-
'<em>',
116-
],
117-
post_tags: [
118-
'</em>',
119-
],
120+
pre_tags: ['<em>'],
121+
post_tags: ['</em>'],
120122
fields: {
121123
'*.analyzed': {},
122124
},
@@ -172,12 +174,8 @@ const mockResourcePath = () => {
172174
},
173175
},
174176
highlight: {
175-
pre_tags: [
176-
'<em>',
177-
],
178-
post_tags: [
179-
'</em>',
180-
],
177+
pre_tags: ['<em>'],
178+
post_tags: ['</em>'],
181179
fields: {
182180
'*.analyzed': {},
183181
},
@@ -212,7 +210,8 @@ const mockArborist = () => {
212210
.persist()
213211
.post('/auth/mapping')
214212
.reply(200, {
215-
'internal-project-1': [ // accessible
213+
'internal-project-1': [
214+
// accessible
216215
{
217216
service: '*',
218217
method: 'create',
@@ -234,13 +233,15 @@ const mockArborist = () => {
234233
method: 'update',
235234
},
236235
],
237-
'internal-project-2': [ // accessible
236+
'internal-project-2': [
237+
// accessible
238238
{
239239
service: '*',
240240
method: 'read',
241241
},
242242
],
243-
'internal-project-3': [ // not accessible since method does not match
243+
'internal-project-3': [
244+
// not accessible since method does not match
244245
{
245246
service: '*',
246247
method: 'create',
@@ -258,19 +259,22 @@ const mockArborist = () => {
258259
method: 'update',
259260
},
260261
],
261-
'internal-project-4': [ // accessible
262+
'internal-project-4': [
263+
// accessible
262264
{
263265
service: '*',
264266
method: '*',
265267
},
266268
],
267-
'internal-project-5': [ // accessible
269+
'internal-project-5': [
270+
// accessible
268271
{
269272
service: 'guppy',
270273
method: '*',
271274
},
272275
],
273-
'internal-project-6': [ // not accessible since service does not match
276+
'internal-project-6': [
277+
// not accessible since service does not match
274278
{
275279
service: 'indexd',
276280
method: '*',
@@ -376,7 +380,9 @@ const mockESMapping = () => {
376380
};
377381

378382
const mockArrayConfig = () => {
379-
const arrayConfigQuery = { query: { ids: { values: ['gen3-dev-subject', 'gen3-dev-file'] } } };
383+
const arrayConfigQuery = {
384+
query: { ids: { values: ['gen3-dev-subject', 'gen3-dev-file'] } },
385+
};
380386
const fakeArrayConfig = {
381387
hits: {
382388
total: 1,
@@ -387,10 +393,7 @@ const mockArrayConfig = () => {
387393
_id: 'gen3-dev-subject',
388394
_score: 1.0,
389395
_source: {
390-
array: [
391-
'some_array_integer_field',
392-
'some_array_string_field',
393-
],
396+
array: ['some_array_integer_field', 'some_array_string_field'],
394397
},
395398
},
396399
],
@@ -405,6 +408,7 @@ const mockArrayConfig = () => {
405408
const setup = () => {
406409
mockArborist();
407410
mockPing();
411+
mockRefresh();
408412
mockResourcePath();
409413
mockESMapping();
410414
mockArrayConfig();

src/server/__tests__/config.test.js

+16-3
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,19 @@ describe('config', () => {
3434

3535
test('should show error if invalid tier access level', async () => {
3636
process.env.TIER_ACCESS_LEVEL = 'invalid-level';
37-
expect(() => (require('../config'))).toThrow(new Error(`Invalid TIER_ACCESS_LEVEL "${process.env.TIER_ACCESS_LEVEL}"`));
37+
expect(() => require('../config')).toThrow(
38+
new Error(`Invalid TIER_ACCESS_LEVEL "${process.env.TIER_ACCESS_LEVEL}"`),
39+
);
3840
});
3941

4042
test('should show error if invalid tier access level in guppy block', async () => {
4143
process.env.TIER_ACCESS_LEVEL = null;
4244
const fileName = './testConfigFiles/test-invalid-index-scoped-tier-access.json';
4345
process.env.GUPPY_CONFIG_FILEPATH = `${__dirname}/${fileName}`;
4446
const invalidItemType = 'subject_private';
45-
expect(() => (require('../config'))).toThrow(new Error(`tier_access_level invalid for index ${invalidItemType}.`));
47+
expect(() => require('../config')).toThrow(
48+
new Error(`tier_access_level invalid for index ${invalidItemType}.`),
49+
);
4650
});
4751

4852
test('clears out site-wide default tiered-access setting if index-scoped levels set', async () => {
@@ -54,7 +58,9 @@ describe('config', () => {
5458
const { indices } = require(fileName);
5559
expect(config.tierAccessLevel).toBeUndefined();
5660
expect(config.tierAccessLimit).toEqual(50);
57-
expect(JSON.stringify(config.esConfig.indices)).toEqual(JSON.stringify(indices));
61+
expect(JSON.stringify(config.esConfig.indices)).toEqual(
62+
JSON.stringify(indices),
63+
);
5864
});
5965

6066
/* --------------- For whitelist --------------- */
@@ -97,4 +103,11 @@ describe('config', () => {
97103
expect(config.esConfig.aggregationIncludeMissingData).toBe(true);
98104
expect(config.esConfig.missingDataAlias).toEqual(alias);
99105
});
106+
107+
/* --------------- For _refresh testing --------------- */
108+
test('could not access _refresh method if not in config', async () => {
109+
process.env.GUPPY_CONFIG_FILEPATH = `${__dirname}/testConfigFiles/test-no-refresh-option-provided.json`;
110+
const config = require('../config').default;
111+
expect(config.allowRefresh).toBe(false);
112+
});
100113
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}

src/server/auth/__tests__/authHelper.test.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// eslint-disable-next-line
2-
import nock from 'nock'; // must import this to enable mock data by nock
2+
import nock from 'nock'; // must import this to enable mock data by nock
33
import getAuthHelperInstance from '../authHelper';
44
import esInstance from '../../es/index';
55
import setupMockDataEndpoint from '../../__mocks__/mockDataFromES';
@@ -12,6 +12,7 @@ setupMockDataEndpoint();
1212
describe('AuthHelper', () => {
1313
test('could create auth helper instance', async () => {
1414
const authHelper = await getAuthHelperInstance('fake-jwt');
15+
expect(authHelper.getCanRefresh()).toEqual(false);
1516
expect(authHelper.getAccessibleResources()).toEqual(['internal-project-1', 'internal-project-2', 'internal-project-4', 'internal-project-5']);
1617
expect(authHelper.getAccessibleResources()).not.toContain(['internal-project-3', 'internal-project-6']);
1718
expect(authHelper.getUnaccessibleResources()).toEqual(['external-project-1', 'external-project-2']);

src/server/auth/arboristClient.js

+2-25
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ class ArboristClient {
88
this.baseEndpoint = arboristEndpoint;
99
}
1010

11-
listAuthorizedResources(jwt) {
11+
listAuthMapping(jwt) {
1212
// Make request to arborist for list of resources with access
1313
const resourcesEndpoint = `${this.baseEndpoint}/auth/mapping`;
14-
log.debug('[ArboristClient] listAuthorizedResources jwt: ', jwt);
14+
log.debug('[ArboristClient] listAuthMapping jwt: ', jwt);
1515

1616
const headers = (jwt) ? { Authorization: `bearer ${jwt}` } : {};
1717
return fetch(
@@ -40,29 +40,6 @@ class ArboristClient {
4040
log.error(err);
4141
throw new CodedError(500, err);
4242
},
43-
).then(
44-
(result) => {
45-
const data = {
46-
resources: [],
47-
};
48-
Object.keys(result).forEach((key) => {
49-
// logic: you have access to a project if you have the following access:
50-
// method 'read' (or '*' - all methods) to service 'guppy' (or '*' - all services)
51-
// on the project resource.
52-
if (result[key] && result[key].some((x) => (
53-
(x.method === 'read' || x.method === '*')
54-
&& (x.service === 'guppy' || x.service === '*')
55-
))) {
56-
data.resources.push(key);
57-
}
58-
});
59-
log.debug('[ArboristClient] data: ', data);
60-
return data;
61-
},
62-
(err) => {
63-
log.error(err);
64-
throw new CodedError(500, err);
65-
},
6643
);
6744
}
6845
}

src/server/auth/authHelper.js

+10-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
getRequestResourceListFromFilter,
66
buildFilterWithResourceList,
77
getAccessibleResourcesFromArboristasync,
8+
checkIfUserCanRefreshServer,
89
} from './utils';
910
import config from '../config';
1011

@@ -15,8 +16,12 @@ export class AuthHelper {
1516

1617
async initialize() {
1718
try {
18-
this._accessibleResourceList = await getAccessibleResourcesFromArboristasync(this._jwt);
19+
const [accessibleResourceList, arboristResources] = await getAccessibleResourcesFromArboristasync(this._jwt);
20+
this._accessibleResourceList = accessibleResourceList;
21+
this._arboristResources = arboristResources;
1922
log.debug('[AuthHelper] accessible resources:', this._accessibleResourceList);
23+
this._canRefresh = await checkIfUserCanRefreshServer(this._arboristResources);
24+
log.debug('[AuthHelper] can user refresh:', this._canRefresh);
2025

2126
const promiseList = [];
2227
config.esConfig.indices.forEach(({ index, type }) => {
@@ -39,6 +44,10 @@ export class AuthHelper {
3944
return this._accessibleResourceList;
4045
}
4146

47+
getCanRefresh() {
48+
return this._canRefresh;
49+
}
50+
4251
getUnaccessibleResources() {
4352
return this._unaccessibleResourceList;
4453
}

src/server/auth/utils.js

+47-3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,23 @@ import arboristClient from './arboristClient';
66
import CodedError from '../utils/error';
77
import config from '../config';
88

9+
export const resourcePathsWithServiceMethodCombination = (userAuthMapping, services, methods = {}) => {
10+
const data = {
11+
resources: [],
12+
};
13+
Object.keys(userAuthMapping).forEach((key) => {
14+
// logic: you have access to a project if you have
15+
// access to any of the combinations made by the method and service lists
16+
if (userAuthMapping[key] && userAuthMapping[key].some((x) => (
17+
methods.includes(x.method)
18+
&& services.includes(x.service)
19+
))) {
20+
data.resources.push(key);
21+
}
22+
});
23+
return data;
24+
};
25+
926
export const getAccessibleResourcesFromArboristasync = async (jwt) => {
1027
let data;
1128
if (config.internalLocalTest) {
@@ -16,7 +33,7 @@ export const getAccessibleResourcesFromArboristasync = async (jwt) => {
1633
],
1734
};
1835
} else {
19-
data = await arboristClient.listAuthorizedResources(jwt);
36+
data = await arboristClient.listAuthMapping(jwt);
2037
}
2138

2239
log.debug('[authMiddleware] list resources: ', JSON.stringify(data, null, 4));
@@ -27,8 +44,35 @@ export const getAccessibleResourcesFromArboristasync = async (jwt) => {
2744
}
2845
throw new CodedError(data.error.code, data.error.message);
2946
}
30-
const resources = data.resources ? _.uniq(data.resources) : [];
31-
return resources;
47+
48+
const read = resourcePathsWithServiceMethodCombination(data, ['guppy', '*'], ['read', '*']);
49+
const readResources = read.resources ? _.uniq(read.resources) : [];
50+
return [readResources, data];
51+
};
52+
53+
export const checkIfUserCanRefreshServer = async (passedData) => {
54+
let data = passedData;
55+
if (config.internalLocalTest) {
56+
data = {
57+
resources: [ // these are just for testing
58+
'/programs/DEV/projects/test',
59+
'/programs/jnkns/projects/jenkins',
60+
],
61+
};
62+
}
63+
64+
log.debug('[authMiddleware] list resources: ', JSON.stringify(data, null, 4));
65+
if (data && data.error) {
66+
// if user is not in arborist db, assume has no access to any
67+
if (data.error.code === 404) {
68+
return false;
69+
}
70+
throw new CodedError(data.error.code, data.error.message);
71+
}
72+
const adminAccess = resourcePathsWithServiceMethodCombination(data, ['guppy'], ['admin_access', '*']);
73+
74+
// Only guppy_admin resource path can control guppy admin access
75+
return adminAccess.resources ? adminAccess.resources.includes('/guppy_admin') : false;
3276
};
3377

3478
export const getRequestResourceListFromFilter = async (

0 commit comments

Comments
 (0)