Skip to content

Commit

Permalink
capabilities satisifiers
Browse files Browse the repository at this point in the history
  • Loading branch information
bucko13 committed Feb 10, 2022
1 parent 4c75ea8 commit 09cefb8
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 1 deletion.
62 changes: 61 additions & 1 deletion src/satisfiers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import {
InvalidServicesError,
SERVICES_CAVEAT_CONDITION,
decodeServicesCaveat,
InvalidCapabilitiesError,
SERVICE_CAPABILITIES_SUFFIX,
decodeCapabilitiesValue,
} from '.'

/**
Expand Down Expand Up @@ -44,24 +47,31 @@ export const createServicesSatisfier = (targetService: string): Satisfier => {
satisfyPrevious: (prev: Caveat, curr: Caveat): boolean => {
const prevServices = decodeServicesCaveat(prev.value.toString())
const currentServices = decodeServicesCaveat(curr.value.toString())
let previouslyAllowed = new Map()

// making typescript happy
if (!Array.isArray(prevServices) || !Array.isArray(currentServices))
throw new InvalidServicesError()

// Construct a set of the services we were previously
// allowed to access.
let previouslyAllowed = new Map()
previouslyAllowed = prevServices.reduce(
(prev, current) => prev.set(current.name, current.tier),
previouslyAllowed
)

// The caveat should not include any new services that
// weren't previously allowed.
for (const service of currentServices) {
if (!previouslyAllowed.has(service.name)) return false
// confirm that previous service tier cannot be higher than current
const prevTier: number = previouslyAllowed.get(service.name)
if (prevTier > service.tier) return false
}

return true
},

satisfyFinal: (caveat: Caveat): boolean => {
const services = decodeServicesCaveat(caveat.value.toString())
// making typescript happy
Expand All @@ -74,3 +84,53 @@ export const createServicesSatisfier = (targetService: string): Satisfier => {
},
}
}

export const createCapabilitiesSatisfier = (
service: string,
targetCapability: string
): Satisfier => {
// validate targetService
if (typeof targetCapability !== 'string') throw new InvalidCapabilitiesError()
if (typeof service !== 'string') throw new InvalidCapabilitiesError()

return {
condition: service + SERVICE_CAPABILITIES_SUFFIX,
satisfyPrevious: (prev: Caveat, curr: Caveat): boolean => {
const prevCapabilities = decodeCapabilitiesValue(prev.value.toString())
const currentCapabilities = decodeCapabilitiesValue(curr.value.toString())

// making typescript happy
if (
!Array.isArray(prevCapabilities) ||
!Array.isArray(currentCapabilities)
)
throw new InvalidServicesError()

// Construct a set of the service's capabilities we were
// previously allowed to access.
let previouslyAllowed = new Set()
previouslyAllowed = prevCapabilities.reduce(
(prev, current) => prev.add(current),
previouslyAllowed
)

// The caveat should not include any new service
// capabilities that weren't previously allowed.
for (const capability of currentCapabilities) {
if (!previouslyAllowed.has(capability)) return false
}

return true
},
satisfyFinal: (caveat: Caveat): boolean => {
const capabilities = decodeCapabilitiesValue(caveat.value.toString())
// making typescript happy
if (!Array.isArray(capabilities)) throw new InvalidServicesError()

for (const capability of capabilities) {
if (capability === targetCapability) return true
}
return false
},
}
}
8 changes: 8 additions & 0 deletions src/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,11 @@ export const createNewCapabilitiesCaveat = (
comp: '=',
})
}

export const decodeCapabilitiesValue = (value: string): string[] => {
if (typeof value !== 'string') throw new InvalidCapabilitiesError()
return value
.toString()
.split(',')
.map((s: string) => s.trim())
}
55 changes: 55 additions & 0 deletions tests/satisfiers.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import {
SERVICES_CAVEAT_CONDITION,
InvalidServicesError,
createServicesSatisfier,
SERVICE_CAPABILITIES_SUFFIX,
createCapabilitiesSatisfier,
InvalidCapabilitiesError,
} from '../src'
import { Satisfier } from '../src/types'

Expand Down Expand Up @@ -119,4 +122,56 @@ describe('satisfiers', () => {
expect(runTest(caveats, 'baz')).to.be.false
})
})

describe('capabilities satisfier', () => {
let firstCaveat: Caveat, secondCaveat: Caveat
const service = 'lightning'

beforeEach(() => {
firstCaveat = Caveat.decode(
`${service}${SERVICE_CAPABILITIES_SUFFIX}=read,write`
)
secondCaveat = Caveat.decode(
`${service}${SERVICE_CAPABILITIES_SUFFIX}=read`
)
})

const runTest = (
caveats: Caveat[],
targetCapability: string
): boolean | Error => {
const satisfier = createCapabilitiesSatisfier(service, targetCapability)
return verifyCaveats(caveats, satisfier)
}

it('should fail to create satisfier on invalid inputs', () => {
const invalidInputs = [12, { foo: 'bar' }, ['a', 'b', 'c']]
for (const target of invalidInputs) {
// @ts-expect-error
expect(() => createCapabilitiesSatisfier(target, 'test')).to.throw(
InvalidCapabilitiesError
)
// @ts-expect-error
expect(() => createCapabilitiesSatisfier('test', target)).to.throw(
InvalidCapabilitiesError
)
}
})

it('should not allow any capabilities that were not previously allowed', () => {
// const invalidCaveat = Caveat.decode(`${SERVICES_CAVEAT_CONDITION}=baz:0`)
const caveats = [secondCaveat, firstCaveat]
expect(runTest(caveats, 'foo')).to.be.false
})

it('should validate for the specified target capabilities', () => {
const caveats = [firstCaveat, secondCaveat]
expect(runTest(caveats, 'read')).to.be.true
// second caveat only has read which is an attenuation with restricted permissions
// so doesn't have write
expect(runTest(caveats, 'write')).to.be.false
// only the first caveat means it has both read and write
expect(runTest([firstCaveat], 'write')).to.be.true
})
})
})
9 changes: 9 additions & 0 deletions tests/service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
InvalidCapabilitiesError,
createNewCapabilitiesCaveat,
SERVICE_CAPABILITIES_SUFFIX,
decodeCapabilitiesValue,
} from '../src/service'

describe('services', () => {
Expand Down Expand Up @@ -124,4 +125,12 @@ describe('services', () => {
}
}
})

it('can decode capabilities caveat value', () => {
// @ts-expect-error
expect(() => decodeCapabilitiesValue(3)).to.throw(InvalidCapabilitiesError)
const value = 'add, subtract,multiply'
const expected = ['add', 'subtract', 'multiply']
expect(decodeCapabilitiesValue(value)).to.have.all.members(expected)
})
})

0 comments on commit 09cefb8

Please sign in to comment.