Skip to content

Commit

Permalink
Merge pull request #37 from gchq/feature/authorisation
Browse files Browse the repository at this point in the history
Allow authorisation of models / deployments
  • Loading branch information
a3957273 authored Mar 14, 2022
2 parents 96aacd2 + 6012f8b commit 26a9b13
Show file tree
Hide file tree
Showing 28 changed files with 459 additions and 166 deletions.
1 change: 1 addition & 0 deletions config/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ http {
proxy_set_header X-Insecure "true";
proxy_set_header X-UserId $req_userid;
proxy_set_header X-Email "[email protected]";
proxy_set_header X-User '{"some":"data"}';

client_max_body_size 0;
chunked_transfer_encoding on;
Expand Down
2 changes: 1 addition & 1 deletion data/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import qs from 'qs'
import { fetcher } from '../utils/fetcher'
import { Deployment, Model, Schema, Version } from '../types/interfaces'

export type ListModelType = 'favourites' | 'mine' | 'all'
export type ListModelType = 'favourites' | 'user' | 'all'
export function useListModels(type: ListModelType, filter?: string) {
const { data, error, mutate } = useSWR<{
models: Array<Model>
Expand Down
2 changes: 1 addition & 1 deletion data/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import qs from 'qs'
import { fetcher } from '../utils/fetcher'

export type RequestType = 'Upload' | 'Deployment'
export type ReviewFilterType = 'mine' | 'all'
export type ReviewFilterType = 'user' | 'all'
export function useListRequests(type: RequestType, filter: ReviewFilterType) {
const { data, error, mutate } = useSWR<{
requests: Array<Request>
Expand Down
4 changes: 2 additions & 2 deletions lib/node/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,11 @@ class Model {
}
}

type ModelsType = 'favourites' | 'mine' | 'all'
type ModelsType = 'favourites' | 'user' | 'all'
type SchemaUse = 'UPLOAD' | 'DEPLOYMENT'

type RequestUse = 'Upload' | 'Deployment'
type RequestFilter = 'all' | 'mine'
type RequestFilter = 'all' | 'user'

interface File {
stream: any
Expand Down
2 changes: 1 addition & 1 deletion pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export default function ExploreModels() {
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<Tabs value={group} onChange={handleGroupChange} aria-label='basic tabs example' indicatorColor='secondary'>
<Tab label='All Models' value='all' />
<Tab label='My Models' value='mine' />
<Tab label='My Models' value='user' />
<Tab label='Favourites' value='favourites' />
</Tabs>
</Box>
Expand Down
4 changes: 2 additions & 2 deletions pages/review.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import MultipleErrorWrapper from '../src/errors/MultipleErrorWrapper'
import { postEndpoint } from '../data/api'

export default function Review() {
const [value, setValue] = useState<ReviewFilterType>('mine')
const [value, setValue] = useState<ReviewFilterType>('user')

const handleChange = (_event: React.SyntheticEvent, newValue: ReviewFilterType) => {
setValue(newValue)
Expand All @@ -34,7 +34,7 @@ export default function Review() {
<Wrapper title='Reviews' page={'review'}>
<>
<Tabs indicatorColor='secondary' value={value} onChange={handleChange}>
<Tab value='mine' label='My approvals' />
<Tab value='user' label='My approvals' />
<Tab value='all' label='All approvals (Admin)' />
</Tabs>
<ApprovalList type={'Upload'} category={value} />
Expand Down
9 changes: 9 additions & 0 deletions server/external/Authorisation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// This file is intended to be static. You may alter this file without
// worrying about merge conflicts later on.

// TypeScript will ensure at build time that any updates made to the
// Authorisation layout are reflected in your class.

import AuthorisationBase from '../utils/AuthorisationBase'

export default AuthorisationBase
3 changes: 3 additions & 0 deletions server/models/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ const UserSchema = new Schema(

// uuidv4() is cryptographically safe
token: { type: String, required: true, default: uuidv4(), select: false },

// mixed user information provided by authorisation
data: { type: Schema.Types.Mixed },
},
{
timestamps: true,
Expand Down
27 changes: 14 additions & 13 deletions server/processors/processDeployments.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import DeploymentModel from '../models/Deployment'
import { deploymentQueue } from '../utils/queues'
import config from 'config'
import prettyMs from 'pretty-ms'
import https from 'https'
import logger from '../utils/logger'
import { getAccessToken } from '../routes/v1/registryAuth'
import UserModel from '../models/User'
import { getUserByInternalId } from '../services/user'
import { findDeploymentById, markDeploymentBuilt } from '../services/deployment'

const httpsAgent = new https.Agent({
rejectUnauthorized: !config.get('registry.insecure'),
Expand All @@ -17,23 +17,24 @@ export default function processDeployments() {
try {
const startTime = new Date()

const { deploymentId } = job.data
const deployment = await DeploymentModel.findById(deploymentId).populate('model')
const { deploymentId, userId } = job.data

const dlog = logger.child({ deploymentId: deployment._id })
const user = await getUserByInternalId(userId)

if (!deployment) {
dlog.error('Unable to find deployment')
throw new Error('Unable to find deployment')
if (!user) {
logger.error('Unable to find deployment owner')
throw new Error('Unable to find deployment owner')
}

const user = await UserModel.findById(deployment.owner)
const deployment = await findDeploymentById(user, deploymentId, { populate: true })

if (!user) {
dlog.error('Unable to find deployment owner')
throw new Error('Unable to find deployment owner')
if (!deployment) {
logger.error('Unable to find deployment')
throw new Error('Unable to find deployment')
}

const dlog = logger.child({ deploymentId: deployment._id })

const { modelID, initialVersionRequested } = deployment.metadata.highLevelDetails

const registry = `https://${config.get('registry.host')}/v2`
Expand Down Expand Up @@ -119,7 +120,7 @@ export default function processDeployments() {

deployment.log('info', 'Finalised new manifest')
dlog.info('Marking build as successful')
await DeploymentModel.findOneAndUpdate({ _id: deployment._id }, { built: true })
await markDeploymentBuilt(deployment._id)

const time = prettyMs(new Date().getTime() - startTime.getTime())
await deployment.log('info', `Processed deployment with tag '${externalImage}' in ${time}`)
Expand Down
16 changes: 8 additions & 8 deletions server/processors/processUploads.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { buildPython } from '../utils/build'
import { uploadQueue } from '../utils/queues'
import prettyMs from 'pretty-ms'
import VersionModel from '../models/Version'
import { findVersionById, markVersionBuilt } from '../services/version'
import logger from '../utils/logger'
import { getUserById } from '../services/user'

export default function processUploads() {
uploadQueue.process(async (job) => {
logger.info({ job: job.data }, 'Started processing upload')
try {
const startTime = new Date()
const version = await VersionModel.findOne({
_id: job.data.versionId,
}).populate('model')

const user = await getUserById(job.data.userId)
const version = await findVersionById(user, job.data.versionId, { populate: true })

const vlog = logger.child({ versionId: version._id })

Expand All @@ -20,17 +21,16 @@ export default function processUploads() {
const tag = await buildPython(version, { binary, code })

vlog.info('Marking build as successful')
await VersionModel.findOneAndUpdate({ _id: version._id }, { built: true })
await markVersionBuilt(version._id)

const time = prettyMs(new Date().getTime() - startTime.getTime())
await version.log('info', `Processed job with tag ${tag} in ${time}`)
} catch (e) {
logger.error({ error: e, versionId: job.data.versionId }, 'Error occurred whilst processing upload')

try {
const version = await VersionModel.findOne({
_id: job.data.versionId,
}).populate('model')
const user = await getUserById(job.data.userId)
const version = await findVersionById(user, job.data.versionId, { populate: true })

await version.log('error', `Failed to process job due to error: '${e}'`)
version.state.build = {
Expand Down
37 changes: 17 additions & 20 deletions server/routes/v1/deployment.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { Request, Response } from 'express'
import bodyParser from 'body-parser'
import ModelModel from '../../models/Model'
import SchemaModel from '../../models/Schema'
import { validateSchema } from '../../utils/validateSchema'
import DeploymentModel from '../../models/Deployment'
import { customAlphabet } from 'nanoid'
import { ensureUserRole } from '../../utils/user'
import VersionModel from '../../models/Version'
import { createDeploymentRequests } from '../../services/request'
import { BadReq, NotFound, Forbidden } from '../../utils/result'
import { findModelByUuid } from '../../services/model'
import { findVersionByName } from '../../services/version'
import { createDeployment, findDeploymentByUuid, findDeployments } from '../../services/deployment'

const nanoid = customAlphabet('0123456789abcdefghijklmnopqrstuvwxyz', 6)

Expand All @@ -17,7 +17,7 @@ export const getDeployment = [
async (req: Request, res: Response) => {
const { uuid } = req.params

const deployment = await DeploymentModel.findOne({ uuid })
const deployment = await findDeploymentByUuid(req.user!, uuid)

if (!deployment) {
throw NotFound({ uuid }, `Unable to find deployment '${uuid}'`)
Expand All @@ -32,9 +32,9 @@ export const getCurrentUserDeployments = [
async (req: Request, res: Response) => {
const { id } = req.params

const deployment = await DeploymentModel.find({ owner: id })
const deployments = await findDeployments(req.user!, { owner: id })

return res.json(deployment)
return res.json(deployments)
},
]

Expand Down Expand Up @@ -62,9 +62,7 @@ export const postDeployment = [
throw NotFound({ errors: schemaIsInvalid }, 'Rejected due to invalid schema')
}

const model = await ModelModel.findOne({
uuid: body.highLevelDetails.modelID,
})
const model = await findModelByUuid(req.user!, body.highLevelDetails.modelID)

if (!model) {
throw NotFound(
Expand All @@ -81,23 +79,20 @@ export const postDeployment = [
const uuid = `${name}-${nanoid()}`
req.log.info({ uuid }, `Named deployment '${uuid}'`)

const deployment = new DeploymentModel({
const deployment = await createDeployment(req.user!, {
schemaRef: body.schemaRef,
uuid: uuid,

model: model._id,
metadata: body,

owner: req.user?._id,
owner: req.user!._id,
})

req.log.info('Saving deployment model')
await deployment.save()

const version = await VersionModel.findOne({
model: model._id,
version: body.highLevelDetails.initialVersionRequested,
})
const version = await findVersionByName(req.user!, model._id, body.highLevelDetails.initialVersionRequested)

if (!version) {
throw NotFound(
Expand All @@ -121,17 +116,19 @@ export const resetDeploymentApprovals = [
async (req: Request, res: Response) => {
const user = req.user
const { uuid } = req.params
const deployment = await DeploymentModel.findOne({ uuid })
const deployment = await findDeploymentByUuid(req.user!, uuid)
if (!deployment) {
throw BadReq({ uuid }, `Unabled to find version for requested deployment: '${uuid}'`)
}
if (user?.id !== deployment.metadata.contacts.requester) {
throw Forbidden({}, 'You cannot reset the approvals for a deployment you do not own.')
}
const version = await VersionModel.findOne({
model: deployment.model,
version: deployment.metadata.highLevelDetails.initialVersionRequested,
})

const version = await findVersionByName(
user!,
deployment.model,
deployment.metadata.highLevelDetails.initialVersionRequested
)
if (!version) {
throw BadReq({ uuid }, `Unabled to find version for requested deployment: '${uuid}'`)
}
Expand Down
Loading

0 comments on commit 26a9b13

Please sign in to comment.