Skip to content

Commit

Permalink
Add VisibilityFilter and rework logic that returns Places and Domains
Browse files Browse the repository at this point in the history
    so "open" and "private" work depending on the 'visibility' setting.
  • Loading branch information
Misterblue committed Mar 24, 2021
1 parent 1201064 commit b6799ed
Show file tree
Hide file tree
Showing 12 changed files with 199 additions and 12 deletions.
4 changes: 4 additions & 0 deletions src/Entities/EntityFilters/AccountFilterInfo.ts
Expand Up @@ -179,6 +179,10 @@ export class AccountFilterInfo extends CriteriaFilter {
return ret;
};

public async criteriaTestAsync(pThingy: any): Promise<boolean> {
return this.criteriaTest(pThingy);
};

// Return the MongoDB query parameters for the search criteria
public criteriaParameters(): any {
this._doingQuery = true;
Expand Down
5 changes: 5 additions & 0 deletions src/Entities/EntityFilters/AccountScopeFilter.ts
Expand Up @@ -18,6 +18,7 @@ import { CriteriaFilter } from '@Entities/EntityFilters/CriteriaFilter';
import { Accounts } from '@Entities/Accounts';
import { AccountEntity } from '@Entities/AccountEntity';
import { Logger } from '@Tools/Logging';
import { prototype } from 'events';

// AccountScopeFilter filters a query stream to the accounts the requestor
// can look at. That is, a person can normally see only the domains
Expand Down Expand Up @@ -89,6 +90,10 @@ export class AccountScopeFilter extends CriteriaFilter {
return true;
};

public async criteriaTestAsync(pThingy: any): Promise<boolean> {
return this.criteriaTest(pThingy);
};

public criteriaParameters(): any {
this._doingQuery = true;
const criteria: any = {};
Expand Down
5 changes: 5 additions & 0 deletions src/Entities/EntityFilters/CriteriaFilter.ts
Expand Up @@ -43,6 +43,11 @@ export abstract class CriteriaFilter {
// Test a thing and return 'true' if it should be included in the set
abstract criteriaTest(pThingy: any): boolean;

// A async version of criteriaTest. This is used externally to the
// database filtering and should go away when the aggregate pipeline
// is implemented.
abstract criteriaTestAsync(pThingy: any): Promise<boolean>;

// Return Mongodb criteria for the search query
// This changes what 'criteriaTest' returns since the testing is now
// expected to be in the query.
Expand Down
4 changes: 4 additions & 0 deletions src/Entities/EntityFilters/GenericFilter.ts
Expand Up @@ -48,6 +48,10 @@ export class GenericFilter extends CriteriaFilter {
return true;
};

public async criteriaTestAsync(pThingy: any): Promise<boolean> {
return this.criteriaTest(pThingy);
};

// Return Mongodb criteria for the search query
// This changes what 'criteriaTest' returns since the testing is now
// expected to be in the query.
Expand Down
4 changes: 4 additions & 0 deletions src/Entities/EntityFilters/PaginationInfo.ts
Expand Up @@ -84,6 +84,10 @@ export class PaginationInfo extends CriteriaFilter {
return true;
};

public async criteriaTestAsync(pThingy: any): Promise<boolean> {
return this.criteriaTest(pThingy);
};

public criteriaParameters(): any {
// this._doingQuery = true;
return {
Expand Down
9 changes: 4 additions & 5 deletions src/Entities/EntityFilters/PlaceFilterInfo.ts
Expand Up @@ -18,7 +18,6 @@ import { PlaceEntity } from '@Entities/PlaceEntity';

import { CriteriaFilter } from '@Entities/EntityFilters/CriteriaFilter';
import { Maturity } from '@Entities/Sets/Maturity';
import { Visibility } from '@Entities/Sets/Visibility';

import { VKeyedCollection } from '@Tools/vTypes';
import { Logger } from '@Tools/Logging';
Expand Down Expand Up @@ -174,6 +173,10 @@ export class PlaceFilterInfo extends CriteriaFilter {
return ret;
};

public async criteriaTestAsync(pThingy: any): Promise<boolean> {
return this.criteriaTest(pThingy);
};

// Return the MongoDB query parameters for the search criteria
public criteriaParameters(): any {
this._doingQuery = true;
Expand All @@ -190,10 +193,6 @@ export class PlaceFilterInfo extends CriteriaFilter {
/* tslint:disable-next-line */
criteria['name'] = { '$regex': this._search, '$options': 'i' }
};
/* tslint:disable-next-line */
criteria['$or'] = [ { 'visibility': { '$exists': false }},
{ 'visibility': Visibility.OPEN },
];
return criteria;
};

Expand Down
4 changes: 4 additions & 0 deletions src/Entities/EntityFilters/RequestScopeFilter.ts
Expand Up @@ -103,6 +103,10 @@ export class RequestScopeFilter extends CriteriaFilter {
return true;
};

public async criteriaTestAsync(pThingy: any): Promise<boolean> {
return this.criteriaTest(pThingy);
};

public criteriaParameters(): any {
this._doingQuery = true;
let getAccountId = this._accessingAcct.id;
Expand Down
146 changes: 146 additions & 0 deletions src/Entities/EntityFilters/VisibilityFilter.ts
@@ -0,0 +1,146 @@
// Copyright 2021 Vircadia Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
'use strict'

import { Request } from 'express';
import { CriteriaFilter } from '@Entities/EntityFilters/CriteriaFilter';
import { Visibility } from '@Entities/Sets/Visibility';
import { Accounts } from '@Entities/Accounts';
import { Domains } from '@Entities/Domains';
import { AccountEntity } from '@Entities/AccountEntity';
import { Logger } from '@Tools/Logging';
import { DomainEntity } from '@Entities/DomainEntity';

// NOTE NOTE NOTE NOTE NOTE
// This filter can't be used as a usual criteria filter because the test
// requires a DB $LOOKUP operation (to lookup the account of the Place's domain)
// and this function can't be done until the DB queries use the aggregate() operation.
// When the DB queries are changed, this can be reworked into a DB filter.
// Until then, use the criteriaTestAsync inline to filter.
// NOTE NOTE NOTE NOTE NOTE
// Filter entities that have a 'visibility' field.
// Visibility of such entities depends on the requestor.
// An admin account can make the request 'asAdmin' to see all Places
// &asAdmin=true
export class VisibilityFilter extends CriteriaFilter {
private _asAdmin: boolean = false;
private _accessingAcct: AccountEntity;
private _accessingAcctName: string;
private _friends: string[] = [];
private _connections: string[] = [];

// Set to 'true' if the pagination was passed in the criteria query parameters
private _doingQuery: boolean = false;

// Note that requesting account can be 'undefined'
public constructor(pRequestorAccount: AccountEntity) {
super();
this._accessingAcct = pRequestorAccount;
return;
}

public parametersFromRequest(pRequest: Request) : void {
try {
if (this._accessingAcct) {
this._accessingAcctName = this._accessingAcct.username;
if (this._accessingAcct.hasOwnProperty('friends')) {
this._friends = this._accessingAcct.friends ?? [];
};
if (this._accessingAcct.hasOwnProperty('connections')) {
this._connections = this._accessingAcct.connections ?? [];
};
if (pRequest.query.hasOwnProperty('asAdmin')) {
const askingForAdmin = pRequest.query.asAdmin === 'true';
if (askingForAdmin) {
if (Accounts.isAdmin(this._accessingAcct)) {
this._asAdmin = true;
};
};
};
};
Logger.cdebug('query-detail', `VisibilityFilter.parametersFromRequest: asAdmin=${this._asAdmin}`);
}
catch (e) {
Logger.error('VisibilityFilter: parameters from request: exception: ' + e);
};
};

// Add any parameters to the response
public addResponseFields(pRequest: Request) {
return;
};

// Return if we've found admin enabling parameters
public AsAdmin(): boolean {
return this._asAdmin;
};

public criteriaTest(pToTest: any): boolean {
let ret = this._doingQuery || this._asAdmin;
if (! ret) {
if (pToTest.hasOwnProperty('visibility')) {
ret = pToTest.visibility === Visibility.OPEN
}
else {
ret = true;
};
}
return ret;
};

public async criteriaTestAsync(pToTest: any, pDomain?: DomainEntity): Promise<boolean> {
let ret = this._doingQuery || this._asAdmin;
if (! ret) {
if (pToTest.hasOwnProperty('visibility')) {
switch (pToTest.visibility) {
case Visibility.OPEN:
ret = true;
break;
case Visibility.PRIVATE:
if (this._accessingAcct && pToTest.hasOwnProperty('domainId')) {
const aDomain = pDomain ?? await Domains.getDomainWithId(pToTest.domainId);
if (aDomain) {
ret = aDomain.sponsorAccountId === this._accessingAcct.id;
};
};
break;
default:
ret = false;
break;
}
}
else {
// if 'visibility' is not specified, it's assumed "OPEN"
ret = true;
};
}
return ret;
};

public criteriaParameters(): any {
this._doingQuery = true;
const criteria: any = {};
if (! this._asAdmin) {
/* tslint:disable-next-line */
criteria['$or'] = [ { 'visibility': { '$exists': false }},
{ 'visibility': Visibility.OPEN },
];
}
return criteria;
};

public sortCriteriaParameters(): any {
return null;
};
};
6 changes: 3 additions & 3 deletions src/Entities/PlaceFields.ts
Expand Up @@ -82,9 +82,9 @@ export const placeFields: { [key: string]: FieldDefn } = {
setter: simpleSetter,
getter: simpleGetter
},
'visiblity': {
entity_field: 'visiblity',
request_field_name: 'visiblity',
'visibility': {
entity_field: 'visibility',
request_field_name: 'visibility',
get_permissions: [ Perm.ALL ],
set_permissions: [ Perm.DOMAINACCESS, Perm.ADMIN ],
validate: async (pField: FieldDefn, pEntity: Entity, pVal: any): Promise<ValidateResponse> => {
Expand Down
2 changes: 2 additions & 0 deletions src/Entities/Sets/Visibility.ts
Expand Up @@ -18,12 +18,14 @@ export class Visibility {
public static OPEN: string = 'open';
public static FRIENDS: string = 'friends';
public static CONNECTIONS: string = 'connections';
public static GROUP: string = 'group';
public static PRIVATE: string = 'private';

static VisibilityCategories = [
Visibility.OPEN,
Visibility.FRIENDS,
Visibility.CONNECTIONS,
Visibility.GROUP,
Visibility.PRIVATE
];

Expand Down
9 changes: 8 additions & 1 deletion src/routes/api/v1/domains.ts
Expand Up @@ -26,6 +26,7 @@ import { buildDomainInfo, buildPlaceInfo } from '@Route-Tools/Util';

import { PaginationInfo } from '@Entities/EntityFilters/PaginationInfo';
import { AccountScopeFilter } from '@Entities/EntityFilters/AccountScopeFilter';
import { VisibilityFilter } from '@Entities/EntityFilters/VisibilityFilter';
import { HTTPStatusCode } from '@Route-Tools/RESTResponse';

import { GenUUID, IsNotNullOrEmpty } from '@Tools/Misc';
Expand All @@ -37,18 +38,24 @@ const procGetDomains: RequestHandler = async (req: Request, resp: Response, next

const pager = new PaginationInfo();
const scoper = new AccountScopeFilter(req.vAuthAccount, "sponsorAccountId");
const visibilitier = new VisibilityFilter(req.vAuthAccount);

pager.parametersFromRequest(req);
scoper.parametersFromRequest(req);
// NOTE: until the DB uses aggregation queries, visibilitier cannot be used as a criteriaFilter
visibilitier.parametersFromRequest(req);

const domainArray: any[] = [];
for await (const aDomain of Domains.enumerateAsync(scoper, pager)) {
domainArray.push( await buildDomainInfoV1(aDomain) );
if (await visibilitier.criteriaTestAsync(req.vAuthAccount, aDomain)) {
domainArray.push( await buildDomainInfoV1(aDomain) );
}
};
req.vRestResp.Data = {
'domains': domainArray
};

visibilitier.addResponseFields(req);
scoper.addResponseFields(req);
pager.addResponseFields(req);
}
Expand Down
13 changes: 10 additions & 3 deletions src/routes/api/v1/places.ts
Expand Up @@ -29,6 +29,7 @@ import { checkAccessToEntity } from '@Route-Tools/Permissions';

import { PaginationInfo } from '@Entities/EntityFilters/PaginationInfo';
import { PlaceFilterInfo } from '@Entities/EntityFilters/PlaceFilterInfo';
import { VisibilityFilter } from '@Entities/EntityFilters/VisibilityFilter';
import { Maturity } from '@Entities/Sets/Maturity';

import { IsNullOrEmpty, IsNotNullOrEmpty } from '@Tools/Misc';
Expand All @@ -40,16 +41,21 @@ const procGetPlaces: RequestHandler = async (req: Request, resp: Response, next:
// if (req.vAuthAccount) {
const pager = new PaginationInfo();
const placer = new PlaceFilterInfo();
const visibilitier = new VisibilityFilter(req.vAuthAccount);

pager.parametersFromRequest(req);
placer.parametersFromRequest(req);
// NOTE: until the DB uses aggregation queries, visibilitier cannot be used as a criteriaFilter
visibilitier.parametersFromRequest(req);

// Loop through all the filtered accounts and create array of info
const places: any[] = [];
for await (const place of Places.enumerateAsync(pager, placer)) {
for await (const place of Places.enumerateAsync(placer, pager)) {
const aDomain = await Domains.getDomainWithId(place.domainId);
if (aDomain && IsNotNullOrEmpty(aDomain.networkAddr)) {
places.push(await buildPlaceInfo(place));
if (await visibilitier.criteriaTestAsync(place, aDomain)) {
if (aDomain && IsNotNullOrEmpty(aDomain.networkAddr)) {
places.push(await buildPlaceInfo(place, aDomain));
};
};
};

Expand All @@ -58,6 +64,7 @@ const procGetPlaces: RequestHandler = async (req: Request, resp: Response, next:
'maturity-categories': Maturity.MaturityCategories
};

visibilitier.addResponseFields(req);
placer.addResponseFields(req);
pager.addResponseFields(req);
// }
Expand Down

0 comments on commit b6799ed

Please sign in to comment.