Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow authorisation of models / deployments #37

Merged
merged 6 commits into from
Mar 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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