Skip to content

Commit 6d6be23

Browse files
author
John Darrington
committed
Merge branch 'development' into 'master'
Development See merge request b650/Deep-Lynx!333
2 parents 03b3cd5 + 6aeb6fc commit 6d6be23

File tree

11 files changed

+143
-69
lines changed

11 files changed

+143
-69
lines changed

AdminWebApp/src/api/client.ts

+4
Original file line numberDiff line numberDiff line change
@@ -895,6 +895,10 @@ export class Client {
895895
return this.get<string[]>(`/containers/${containerID}/users/${userID}/roles`);
896896
}
897897

898+
removeAllUserRoles(containerID: string, userID: string): Promise<boolean> {
899+
return this.delete(`/containers/${containerID}/users/${userID}/roles`);
900+
}
901+
898902
retrieveTypeMapping(containerID: string, dataSourceID: string, typeMappingID: string): Promise<TypeMappingT> {
899903
return this.get<TypeMappingT>(`/containers/${containerID}/import/datasources/${dataSourceID}/mappings/${typeMappingID}`);
900904
}

AdminWebApp/src/auth/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export type UserT = {
22
id: string;
3+
role: string;
34
identity_provider_id: string;
45
identity_provider: string;
56
display_name: string;

AdminWebApp/src/views/ContainerUsers.vue

+33
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,28 @@
1616
<invite-user-to-container-dialog :containerID="containerID" @userInvited="flashSuccess"></invite-user-to-container-dialog>
1717
</v-toolbar>
1818
</template>
19+
<template v-slot:[`item.role`]="{ item }">
20+
<div v-if="$store.getters.activeContainer.created_by === item.id">Owner</div>
21+
<div v-else>{{retrieveUserRole(item)}} {{item.role}}</div>
22+
</template>
23+
1924
<template v-slot:[`item.actions`]="{ item }">
2025
<v-icon
26+
v-if="$store.getters.activeContainer.created_by !== item.id || item.id !== $auth.CurrentUser().id"
2127
small
2228
class="mr-2"
2329
@click="editUser(item)"
2430
>
2531
mdi-pencil
2632
</v-icon>
33+
<v-icon
34+
v-if="$store.getters.activeContainer.created_by !== item.id || item.id !== $auth.CurrentUser().id"
35+
small
36+
class="mr-2"
37+
@click="deleteUser(item)"
38+
>
39+
mdi-account-multiple-minus
40+
</v-icon>
2741
</template>
2842
</v-data-table>
2943

@@ -108,6 +122,7 @@
108122
return [
109123
{ text: this.$t("containerUsers.name"), value: 'display_name' },
110124
{ text: this.$t("containerUsers.email"), value: 'email'},
125+
{ text: this.$t("containerUsers.role"), value: 'role'},
111126
{ text: this.$t("containerUsers.actions"), value: 'actions', sortable: false }
112127
]
113128
}
@@ -134,6 +149,16 @@
134149
}
135150
}
136151
152+
retrieveUserRole(user: UserT) {
153+
this.$client.retrieveUserRoles(this.containerID, user.id)
154+
.then(roles => {
155+
if(roles.length > 0) {
156+
user.role = roles[0]
157+
}
158+
})
159+
.catch(e => this.errorMessage = e)
160+
}
161+
137162
138163
retrieveUserRoles(user: UserT) {
139164
if(this.toEdit) {
@@ -153,6 +178,14 @@
153178
this.retrieveUserRoles(user)
154179
}
155180
181+
deleteUser(user: UserT) {
182+
this.$client.removeAllUserRoles(this.containerID, user.id!)
183+
.then(() => {
184+
this.refreshUsers();
185+
})
186+
.catch(e => this.errorMessage = e)
187+
}
188+
156189
flashSuccess(){
157190
this.inviteSuccess = this.$t('containerUsers.successfullyInvited') as string
158191
}

src/data_access_layer/mappers/data_warehouse/data/edge_queue_item_mapper.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export default class EdgeQueueItemMapper extends Mapper {
4747
});
4848
}
4949

50-
public async SetNextAttemptAt(id: string, nextRunDate: Date, error?: string): Promise<Result<boolean>> {
50+
public async SetNextAttemptAt(id: string, nextRunDate: Date | string, error?: string): Promise<Result<boolean>> {
5151
return super.runStatement(this.setNextRunAtStatement(id, nextRunDate, error));
5252
}
5353

@@ -73,7 +73,7 @@ export default class EdgeQueueItemMapper extends Mapper {
7373
return format(text, values);
7474
}
7575

76-
private setNextRunAtStatement(id: string, nextAttemptDate: Date, error?: string): QueryConfig {
76+
private setNextRunAtStatement(id: string, nextAttemptDate: Date | string, error?: string): QueryConfig {
7777
if (error) {
7878
return {
7979
text: `UPDATE edge_queue_items SET next_attempt_at = $2, attempts = attempts + 1, error = $3 WHERE id = $1`,
@@ -102,6 +102,6 @@ export default class EdgeQueueItemMapper extends Mapper {
102102
}
103103

104104
public needRetriedStreamingStatement(): string {
105-
return `SElECT * FROM edge_queue_items WHERE next_attempt_at < NOW()`;
105+
return `SElECT * FROM edge_queue_items WHERE next_attempt_at < NOW() AT TIME ZONE 'utc'`;
106106
}
107107
}

src/data_access_layer/repositories/access_management/user_repository.ts

+8-7
Original file line numberDiff line numberDiff line change
@@ -323,15 +323,15 @@ export default class UserRepository extends Repository implements RepositoryInte
323323
return Promise.resolve(Result.Success(await Authorization.AssignRole(payload.user_id!, payload.role_name!, payload.container_id)));
324324
}
325325

326-
async removeAllRoles(user: User, domain: string): Promise<Result<boolean>> {
326+
async removeAllRoles(user: User, userID: string, domain: string): Promise<Result<boolean>> {
327327
// generally the route authorization methods in http_server would handle
328328
// authentication, but I've found that in a few places we need this additional
329329
// check as the route might not have all the information needed to make a
330330
// permissions check when removing roles
331-
const authed = await Authorization.AuthUser(user, 'write', 'users');
331+
const authed = await Authorization.AuthUser(user, 'write', 'users', domain);
332332
if (!authed) return Promise.resolve(Result.Error(ErrorUnauthorized));
333333

334-
const deleted = await Authorization.DeleteAllRoles(user.id!, domain);
334+
const deleted = await Authorization.DeleteAllRoles(userID, domain);
335335

336336
return Promise.resolve(Result.Success(deleted));
337337
}
@@ -471,11 +471,12 @@ export default class UserRepository extends Repository implements RepositoryInte
471471
);
472472
}
473473

474-
const containerUsers = await this.listServiceUsersForContainer(containerID)
475-
if(containerUsers.isError) return Promise.resolve(Result.Pass(containerUsers))
474+
const containerUsers = await this.listServiceUsersForContainer(containerID);
475+
if (containerUsers.isError) return Promise.resolve(Result.Pass(containerUsers));
476476
else {
477-
const found = containerUsers.value.find(user => user.id === userID)
478-
if(!found) return Promise.resolve(Result.Failure('unable to set permissions for service user, service user does not belong to proposed container'))
477+
const found = containerUsers.value.find((user) => user.id === userID);
478+
if (!found)
479+
return Promise.resolve(Result.Failure('unable to set permissions for service user, service user does not belong to proposed container'));
479480
}
480481

481482
return await permissionSet.writePermissions(userID, containerID);

src/data_processing/edge_inserter.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,18 @@ export async function InsertEdge(edgeQueueItem: EdgeQueueItem): Promise<Result<b
3030

3131
// if we failed, need to iterate the attempts and set the next attempt date, so we don't swamp the database - this
3232
// is an exponential backoff
33-
edgeQueueItem.next_attempt_at.setSeconds(
34-
edgeQueueItem.next_attempt_at.getSeconds() + Math.pow(Config.edge_insertion_backoff_multiplier, edgeQueueItem.attempts++),
33+
const currentTime = new Date().getTime();
34+
35+
edgeQueueItem.next_attempt_at = new Date(
36+
currentTime + (edgeQueueItem.next_attempt_at.getSeconds() + Math.pow(Config.edge_insertion_backoff_multiplier, edgeQueueItem.attempts++)) * 1000,
3537
);
3638

37-
const set = await queueMapper.SetNextAttemptAt(edgeQueueItem.id!, edgeQueueItem.next_attempt_at, inserted.error?.error);
39+
const set = await queueMapper.SetNextAttemptAt(edgeQueueItem.id!, edgeQueueItem.next_attempt_at.toISOString(), inserted.error?.error);
3840
if (set.isError) {
3941
Logger.debug(`unable to set next retry time for edge queue item ${set.error?.error}`);
4042
}
4143

44+
await Cache.del(`edge_insertion_${edgeQueueItem.id}`);
4245
return Promise.resolve(Result.Failure(`unable to save edge ${inserted.error?.error}`));
4346
}
4447

@@ -75,9 +78,11 @@ export async function InsertEdge(edgeQueueItem: EdgeQueueItem): Promise<Result<b
7578
await mapper.rollbackTransaction(transaction.value);
7679
Logger.debug(`unable to delete edge queue item: ${deleted.error?.error}`);
7780

81+
await Cache.del(`edge_insertion_${edgeQueueItem.id}`);
7882
return Promise.resolve(Result.Failure(`unable to delete edge queue item ${deleted.error?.error}`));
7983
}
8084

8185
await mapper.completeTransaction(transaction.value);
86+
await Cache.del(`edge_insertion_${edgeQueueItem.id}`);
8287
return Promise.resolve(Result.Success(true));
8388
}

src/http_server/routes/access_management/oauth_routes.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const keyRepo = new KeyPairRepository();
2525
const oauthRepo = new OAuthRepository();
2626
import DOMPurify from 'isomorphic-dompurify';
2727
import Result from '../../../common_classes/result';
28+
import Logger from '../../../services/logger';
2829

2930
/*
3031
OAuthRoutes contain all routes pertaining to oauth application management and
@@ -407,7 +408,7 @@ export default class OAuthRoutes {
407408
return res.redirect(req.query.redirect_uri as string);
408409
}
409410

410-
return res.redirect('/oauth');
411+
res.redirect('/oauth');
411412
}
412413

413414
private static login(req: Request, res: Response, next: NextFunction) {

src/http_server/routes/access_management/user_routes.ts

+16
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export default class UserRoutes {
4040

4141
app.post('/containers/:containerID/users/roles', ...middleware, authInContainer('write', 'users'), this.assignRole);
4242
app.get('/containers/:containerID/users/:userID/roles', ...middleware, authInContainer('read', 'users'), this.listUserRoles);
43+
app.delete('/containers/:containerID/users/:userID/roles', ...middleware, authInContainer('read', 'users'), this.removeAllUserRoles);
4344

4445
app.post('/containers/:containerID/users/invite', ...middleware, authInContainer('write', 'users'), this.inviteUserToContainer);
4546
app.get('/containers/:containerID/users/invite', ...middleware, authInContainer('read', 'users'), this.listInvitedUsers);
@@ -191,6 +192,21 @@ export default class UserRoutes {
191192
}
192193
}
193194

195+
private static removeAllUserRoles(req: Request, res: Response, next: NextFunction) {
196+
if (req.routeUser && req.container && req.currentUser) {
197+
userRepo
198+
.removeAllRoles(req.currentUser, req.routeUser.id!, req.container.id!)
199+
.then((result) => {
200+
result.asResponse(res);
201+
})
202+
.catch((err) => Result.Error(err).asResponse(res))
203+
.finally(() => next());
204+
} else {
205+
Result.Failure('user not found', 404).asResponse(res);
206+
next();
207+
}
208+
}
209+
194210
private static listUserPermissions(req: Request, res: Response, next: NextFunction) {
195211
if (req.currentUser) {
196212
userRepo

src/http_server/views/login.hbs

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
</div>
4242
</div>
4343
<div class='w-100 px-4'>
44+
<input type="submit" style="display: none" />
4445
<button type='submit' class='btn btn-lg btn-custom mb-2 btn-block'>Login</button>
4546
</div>
4647
{{/unless}}

src/jobs/edge_queue_emitter.ts

+31-23
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import QueryStream from 'pg-query-stream';
88
import Logger from '../services/logger';
99
import Cache from '../services/cache/cache';
1010
import Config from '../services/config';
11-
import {plainToClass, classToPlain} from 'class-transformer';
11+
import {plainToClass, classToPlain, instanceToPlain, plainToInstance} from 'class-transformer';
1212
import {parentPort} from 'worker_threads';
1313
import EdgeQueueItemMapper from '../data_access_layer/mappers/data_warehouse/data/edge_queue_item_mapper';
1414
import {EdgeQueueItem} from '../domain_objects/data_warehouse/data/edge';
@@ -26,29 +26,35 @@ void postgresAdapter
2626
const emitter = () => {
2727
void postgresAdapter.Pool.connect((err, client, done) => {
2828
const stream = client.query(new QueryStream(mapper.needRetriedStreamingStatement()));
29+
const promises: Promise<boolean>[] = [];
2930
const putPromises: Promise<boolean>[] = [];
3031

3132
stream.on('data', (data) => {
32-
const item = plainToClass(EdgeQueueItem, data as object);
33+
const item = plainToInstance(EdgeQueueItem, data as object);
3334

3435
// check to see if the edge queue item is in the cache, indicating that there is a high probability that
3536
// this message is already in the queue and either is being processed or waiting to be processed
36-
Cache.get(`edge_insertion_${item.id}`)
37-
.then((set) => {
38-
if (!set) {
39-
// if the item isn't the cache, we can go ahead and queue data
40-
putPromises.push(queue.Put(Config.edge_insertion_queue, classToPlain(item)));
41-
}
42-
})
43-
// if we error out we need to go ahead and queue this message anyway, just so we're not dropping
44-
// data
45-
.catch((e) => {
46-
Logger.error(`error reading from cache for staging emitter ${e}`);
47-
putPromises.push(queue.Put(Config.edge_insertion_queue, classToPlain(item)));
48-
})
49-
.finally(() => {
50-
void Cache.set(`edge_insertion_${item.id}`, {}, Config.initial_import_cache_ttl);
51-
});
37+
promises.push(
38+
new Promise((resolve) => {
39+
Cache.get(`edge_insertion_${item.id}`)
40+
.then((set) => {
41+
if (!set) {
42+
// if the item isn't the cache, we can go ahead and queue data
43+
putPromises.push(queue.Put(Config.edge_insertion_queue, instanceToPlain(item)));
44+
}
45+
})
46+
// if we error out we need to go ahead and queue this message anyway, just so we're not dropping
47+
// data
48+
.catch((e) => {
49+
Logger.error(`error reading from cache for staging emitter ${e}`);
50+
putPromises.push(queue.Put(Config.edge_insertion_queue, instanceToPlain(item)));
51+
})
52+
.finally(() => {
53+
void Cache.set(`edge_insertion_${item.id}`, {}, Config.initial_import_cache_ttl);
54+
resolve(true);
55+
});
56+
}),
57+
);
5258
});
5359

5460
stream.on('error', (e: Error) => {
@@ -62,11 +68,13 @@ void postgresAdapter
6268
stream.on('end', () => {
6369
done();
6470

65-
Promise.all(putPromises).finally(() => {
66-
if (parentPort) parentPort.postMessage('done');
67-
else {
68-
process.exit(0);
69-
}
71+
Promise.all(promises).finally(() => {
72+
Promise.all(putPromises).finally(() => {
73+
if (parentPort) parentPort.postMessage('done');
74+
else {
75+
process.exit(0);
76+
}
77+
});
7078
});
7179
});
7280

0 commit comments

Comments
 (0)