Skip to content

Commit

Permalink
Refactored everything into privileged-service
Browse files Browse the repository at this point in the history
  • Loading branch information
evert committed Sep 25, 2023
1 parent e3c017b commit 2299910
Show file tree
Hide file tree
Showing 11 changed files with 177 additions and 216 deletions.
3 changes: 1 addition & 2 deletions src/app/controller/item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { Context } from '@curveball/core';
import * as privilegeService from '../../privilege/service';
import * as hal from '../formats/hal';
import { PrincipalService } from '../../principal/privileged-service';
import * as groupService from '../../group/service';

type EditPrincipalBody = {
nickname: string;
Expand Down Expand Up @@ -35,7 +34,7 @@ class AppController extends Controller {
app,
principalPrivileges.getAll(),
isAdmin,
await groupService.findGroupsForPrincipal(app),
await principalService.findGroupsForPrincipal(app),
);

}
Expand Down
13 changes: 6 additions & 7 deletions src/group/controller/item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { Context } from '@curveball/core';
import * as privilegeService from '../../privilege/service';
import * as hal from '../formats/hal';
import { PrincipalService } from '../../principal/privileged-service';
import * as groupService from '../../group/service';
import { NotFound, Conflict } from '@curveball/http-errors';

type EditPrincipalBody = {
Expand Down Expand Up @@ -34,7 +33,7 @@ class GroupController extends Controller {
const principalService = new PrincipalService(ctx.privileges);
const group = await principalService.findByExternalId(ctx.params.id, 'group');
const isAdmin = ctx.privileges.has('admin');
const members = await groupService.findMembers(group);
const members = await principalService.findMembers(group);

const principalPrivileges = await privilegeService.get(group);

Expand All @@ -49,7 +48,7 @@ class GroupController extends Controller {
group,
principalPrivileges.getAll(),
isAdmin,
await groupService.findGroupsForPrincipal(group),
await principalService.findGroupsForPrincipal(group),
members,
);
}
Expand Down Expand Up @@ -97,23 +96,23 @@ class GroupController extends Controller {

switch (ctx.request.body.operation) {
case 'add-member':
await groupService.addMember(group, member);
await principalService.addMember(group, member);
break;
case 'remove-member':
await groupService.removeMember(group, member);
await principalService.removeMember(group, member);
break;
}

if (ctx.request.accepts('text/html')) {
const isAdmin = ctx.privileges.has('admin');
const members = await groupService.findMembers(group);
const members = await principalService.findMembers(group);
const principalPrivileges = await privilegeService.get(group);

ctx.response.body = hal.item(
group,
principalPrivileges.getAll(),
isAdmin,
await groupService.findGroupsForPrincipal(group),
await principalService.findGroupsForPrincipal(group),
members,
);
ctx.redirect(200, group.href);
Expand Down
73 changes: 0 additions & 73 deletions src/group/service.ts

This file was deleted.

4 changes: 3 additions & 1 deletion src/oauth2-client/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Oauth2ClientsRecord } from 'knex/types/tables';
import { wrapError, UniqueViolationError } from 'db-errors';

import { OAuth2Client } from './types';
import * as principalService from '../principal/service';
import { PrincipalService } from '../principal/privileged-service';
import db, { insertAndGetId } from '../database';
import { InvalidRequest } from '../oauth2/errors';
import parseBasicAuth from './parse-basic-auth';
Expand All @@ -23,6 +23,7 @@ export async function findByClientId(clientId: string): Promise<OAuth2Client> {

const record: Oauth2ClientsRecord = result[0];

const principalService = new PrincipalService('insecure');
const app = await principalService.findById(record.user_id, 'app');
if (!app.active) {
throw new Error(`App ${app.nickname} is not active`);
Expand All @@ -43,6 +44,7 @@ export async function findById(id: number): Promise<OAuth2Client> {

const record: Oauth2ClientsRecord = result[0];

const principalService = new PrincipalService('insecure');
const app = await principalService.findById(record.user_id, 'app');
if (!app.active) {
throw new Error(`App ${app.nickname} is not active`);
Expand Down
5 changes: 4 additions & 1 deletion src/oauth2/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as crypto from 'crypto';

import db, { query } from '../database';
import { getSetting } from '../server-settings';
import * as principalService from '../principal/service';
import { PrincipalService } from '../principal/privileged-service';
import { InvalidGrant, InvalidRequest, UnauthorizedClient } from './errors';
import { CodeChallengeMethod, OAuth2Code, OAuth2Token } from './types';
import { OAuth2Client } from '../oauth2-client/types';
Expand Down Expand Up @@ -182,6 +182,7 @@ export async function generateTokenAuthorizationCode(options: GenerateTokenAutho
throw new UnauthorizedClient('The client_id associated with the token did not match with the authenticated client credentials');
}

const principalService = new PrincipalService('insecure');
const user = await principalService.findById(codeRecord.principal_id, 'user');
if (!user.active) {
throw new Error(`User ${user.href} is not active`);
Expand Down Expand Up @@ -511,6 +512,7 @@ export async function getTokenByAccessToken(accessToken: string): Promise<OAuth2
}

const row: Oauth2TokensRecord = result[0];
const principalService = new PrincipalService('insecure');
const principal = await principalService.findById(row.user_id);

if (!principal.active) {
Expand Down Expand Up @@ -541,6 +543,7 @@ export async function getTokenByRefreshToken(refreshToken: string): Promise<OAut

const row = result[0];

const principalService = new PrincipalService('insecure');
const principal = await principalService.findById(row.user_id);
if (!principal.active) {
throw new Error(`Principal ${principal.href} is not active`);
Expand Down
3 changes: 2 additions & 1 deletion src/one-time-token/service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { OneTimeToken } from './types';
import { User } from '../types';
import db from '../database';
import * as principalService from '../principal/service';
import { PrincipalService } from '../principal/privileged-service';
import { BadRequest } from '@curveball/http-errors';
import { generateSecretToken } from '../crypto';

Expand Down Expand Up @@ -43,6 +43,7 @@ export async function validateToken(token: string): Promise<User> {
throw new BadRequest ('Failed to validate token');
} else {
await db.raw('DELETE FROM reset_password_token WHERE token = ?', [token]);
const principalService = new PrincipalService('insecure');
return principalService.findById(result[0][0].user_id) as Promise<User>;
}

Expand Down
147 changes: 147 additions & 0 deletions src/principal/privileged-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,153 @@ export class PrincipalService {
return recordToModel(result);
}

/**
* Find multiple principals.
*
* This function returns the principals as a map, index by their id.
* This is a helper function typically used by other services to find large numbers
* of joined principals from other tables fast.
*
* If any of the ids in the list is duplicated, they are de-duplicated here.
* if any of the provided ids are not found, this function will error.
*/
async findMany(ids: number[]): Promise<Map<number,Principal>> {

this.privileges.require('a12n:principals:list');
const records = await db('principals')
.select()
.whereIn('id', ids);

const result = new Map<number, Principal>(records.map(
record => [record.id, recordToModel(record)]
));

for (const id of ids) {
if (!result.has(id)) {
throw new NotFound(`Principal with ${id} not found`);
}
}

return result;

}

async findById(id: number, type: 'user'): Promise<User>;
async findById(id: number, type: 'group'): Promise<Group>;
async findById(id: number, type: 'app'): Promise<App>;
async findById(id: number): Promise<Principal>;
async findById(id: number, type?: PrincipalType): Promise<Principal> {

this.privileges.require('a12n:principals:list');
const result = await db('principals')
.select()
.where({id});

if (result.length !== 1) {
throw new NotFound(`Principal with id: ${id} not found`);
}

const principal = recordToModel(result[0]);

if (type && principal.type !== type) {
throw new NotFound(`Principal with id ${id} does not have type ${type}`);
}
return principal;

}

/**
* Returns the list of members of a group
*/
async findMembers(group: Group): Promise<Principal[]> {

this.privileges.require('a12n:principals:list');

const result = await db('principals')
.select('principals.*')
.innerJoin('group_members', { 'principals.id': 'group_members.user_id'})
.where({group_id: group.id})
.orderBy('nickname');

const models = [];

for (const record of result) {
const model = recordToModel(record);
models.push(model);
}

return models;

}

async addMember(group: Group, user: Principal): Promise<void> {

this.privileges.require('admin');

await db('group_members').insert({
group_id: group.id,
user_id: user.id
});

}

async replaceMembers(group: Group, users: Principal[]): Promise<void> {

this.privileges.require('admin');
await db.transaction(async trx => {
await trx('group_members')
.delete()
.where({
group_id: group.id
});

for(const user of users) {
await trx('groupmembers')
.insert({
group_id: group.id,
user_id: user.id
});
}
await trx.commit();
});

}

async removeMember(group: Group, user: Principal): Promise<void> {

this.privileges.require('admin');
await db('group_members')
.delete()
.where({
group_id: group.id,
user_id: user.id,
});

}

/**
* Returns a list of groups for which the principal is a member
*/
async findGroupsForPrincipal(principal: Principal): Promise<Group[]> {

this.privileges.require('admin');
const result = await db('principals')
.select('principals.*')
.innerJoin('group_members', { 'principals.id': 'group_members.group_id'})
.where({user_id: principal.id})
.orderBy('nickname');

const models: Group[] = [];

for (const record of result) {
const model = recordToModel(record);
models.push(model as Group);
}

return models;

}

}
/**
* Returns true if more than 1 principal exists in the system.
Expand Down
Loading

0 comments on commit 2299910

Please sign in to comment.