Skip to content

Commit

Permalink
feat: fetch an entity configuration
Browse files Browse the repository at this point in the history
Signed-off-by: Berend Sliedrecht <[email protected]>
  • Loading branch information
berendsliedrecht committed Jul 10, 2024
1 parent c15e2f4 commit a86e90b
Show file tree
Hide file tree
Showing 28 changed files with 728 additions and 65 deletions.
130 changes: 130 additions & 0 deletions packages/core/__tests__/createEntityConfiguration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import assert from 'node:assert'
import { describe, it } from 'node:test'
import { createEntityConfiguration } from '../src/entityConfiguration'
import type { SignCallback } from '../src/utils'

describe('create entity configuration', () => {
const signCallback: SignCallback = () => Promise.resolve(new Uint8Array(42).fill(8))

it('should create a basic entity configuration', async () => {
const entityConfiguration = await createEntityConfiguration({
signCallback,
claims: {
exp: 1,
iat: 1,
iss: 'https://example.org',
sub: 'https://example.org',
jwks: { keys: [{ kid: 'a', kty: 'EC' }] },
},
header: { kid: 'a', typ: 'entity-statement+jwt' },
})

assert(
entityConfiguration,
'eyJraWQiOiJhIiwidHlwIjoiZW50aXR5LXN0YXRlbWVudCtqd3QifQ.eyJpc3MiOiJodHRwczovL2V4YW1wbGUub3JnIiwic3ViIjoiaHR0cHM6Ly9leGFtcGxlLm9yZyIsImlhdCI6IjE5NzAtMDEtMDFUMDA6MDA6MDEuMDAwWiIsImV4cCI6IjE5NzAtMDEtMDFUMDA6MDA6MDEuMDAwWiIsImp3a3MiOnsia2V5cyI6W3sia3R5IjoiRUMiLCJraWQiOiJhIn1dfX0.CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI'
)
})

it('should create a more complex entity configuration', async () => {
const entityConfiguration = await createEntityConfiguration({
signCallback,
claims: {
exp: 1,
iat: 1,
iss: 'https://example.org',
sub: 'https://example.org',
jwks: { keys: [{ kid: 'a', kty: 'EC' }] },
authority_hints: ['https://foo.com'],
},
header: { kid: 'a', typ: 'entity-statement+jwt' },
})

assert(
entityConfiguration,
'eyJraWQiOiJhIiwidHlwIjoiZW50aXR5LXN0YXRlbWVudCtqd3QifQ.eyJpc3MiOiJodHRwczovL2V4YW1wbGUub3JnIiwic3ViIjoiaHR0cHM6Ly9leGFtcGxlLm9yZyIsImlhdCI6IjE5NzAtMDEtMDFUMDA6MDA6MDEuMDAwWiIsImV4cCI6IjE5NzAtMDEtMDFUMDA6MDA6MDEuMDAwWiIsImp3a3MiOnsia2V5cyI6W3sia3R5IjoiRUMiLCJraWQiOiJhIn1dfSwiYXV0aG9yaXR5X2hpbnRzIjpbImh0dHBzOi8vZm9vLmNvbSJdfQ.CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI'
)
})

it('should not create a entity configuration when iss and sub are not equal', async () => {
await assert.rejects(
createEntityConfiguration({
signCallback,
claims: {
exp: 1,
iat: 1,
iss: 'https://some-other.url',
sub: 'https://example.org',
jwks: { keys: [{ kid: 'a', kty: 'EC' }] },
},
header: { kid: 'a', typ: 'entity-statement+jwt' },
}),
{
name: 'ZodError',
}
)
})

it('should not create a entity configuration when kid is not found in jwks.keys', async () => {
await assert.rejects(
createEntityConfiguration({
signCallback,
claims: {
exp: 1,
iat: 1,
iss: 'https://example.org',
sub: 'https://example.org',
jwks: { keys: [{ kid: 'a', kty: 'EC' }] },
},
header: { kid: 'invalid_id', typ: 'entity-statement+jwt' },
}),
{
name: 'Error',
message: "key with id: 'invalid_id' could not be found in the claims",
}
)
})

it("should not create a entity configuration when typ is not 'entity-statement+jwt'", async () => {
await assert.rejects(
createEntityConfiguration({
signCallback,
claims: {
exp: 1,
iat: 1,
iss: 'https://example.org',
sub: 'https://example.org',
jwks: { keys: [{ kid: 'a', kty: 'EC' }] },
},
// @ts-ignore
header: { kid: 'a', typ: 'invalid_typ' },
}),
{
name: 'ZodError',
}
)
})

it('should not create a entity configuration when jwks.keys include keys with the same kid', async () => {
await assert.rejects(
createEntityConfiguration({
signCallback,
claims: {
exp: 1,
iat: 1,
iss: 'https://example.org',
sub: 'https://example.org',
jwks: {
keys: [
{ kid: 'a', kty: 'EC' },
{ kid: 'a', kty: 'EC' },
],
},
},
header: { kid: 'a', typ: 'entity-statement+jwt' },
}),
{
name: 'ZodError',
}
)
})
})
75 changes: 75 additions & 0 deletions packages/core/__tests__/fetchEntityConfiguration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { describe, it } from 'node:test'
import { createEntityConfiguration, fetchEntityConfiguration } from '../src/entityConfiguration'
import type { SignCallback, VerifyCallback } from '../src/utils'

import assert from 'node:assert/strict'
import nock from 'nock'

describe('fetch entity configuration', () => {
const verifyJwtCallback: VerifyCallback = () => Promise.resolve(true)

const signCallback: SignCallback = () => Promise.resolve(new Uint8Array(10).fill(42))

it('should fetch a simple entity configuration', async () => {
const entityId = 'https://example.org'

const claims = {
iss: entityId,
sub: entityId,
iat: new Date(),
exp: new Date(),
jwks: { keys: [{ kid: 'a', kty: 'EC' }] },
}

const entityConfiguration = await createEntityConfiguration({
header: { kid: 'a', typ: 'entity-statement+jwt' },
claims,
signCallback,
})

const scope = nock(entityId).get('/.well-known/openid-federation').reply(200, entityConfiguration, {
'content-type': 'application/entity-statement+jwt',
})

const fetchedEntityConfiguration = await fetchEntityConfiguration({
entityId,
verifyJwtCallback,
})

assert.deepStrictEqual(fetchedEntityConfiguration, claims)

scope.done()
})

it('should not fetch an entity configuration when the content-type is invalid', async () => {
const entityId = 'https://exampletwo.org'

const claims = {
iss: entityId,
sub: entityId,
iat: new Date(),
exp: new Date(),
jwks: { keys: [{ kid: 'a', kty: 'EC' }] },
}

const entityConfiguration = await createEntityConfiguration({
header: { kid: 'a', typ: 'entity-statement+jwt' },
claims,
signCallback,
})

const scope = nock(entityId).get('/.well-known/openid-federation').reply(200, entityConfiguration, {
'content-type': 'invalid-type',
})

await assert.rejects(fetchEntityConfiguration({ entityId, verifyJwtCallback }), { name: 'Error' })

scope.done()
})

it('should not fetch an entity configuration when there is no entity configuration', async () => {
const entityId = 'https://examplethree.org'

await assert.rejects(fetchEntityConfiguration({ entityId, verifyJwtCallback }), { name: 'TypeError' })
})
})
32 changes: 16 additions & 16 deletions packages/core/__tests__/schemas.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import assert from 'node:assert/strict'
import { describe, it } from 'node:test'

import { constraintSchema } from '../src/constraints'
import { entityConfigurationSchema } from '../src/entityConfiguration'
import { entityConfigurationClaimsSchema } from '../src/entityConfiguration'
import { entityStatementClaimsSchema } from '../src/entityStatement'
import { metadataSchema } from '../src/metadata/metadata'
import { trustMarkClaimsSchema } from '../src/trustMark'
Expand Down Expand Up @@ -41,28 +41,28 @@ import { trustMarkClaimsFigure20 } from './fixtures/trustmarkClaimsFigure20'

describe('zod validation schemas', () => {
describe('validate valid test vectors', () => {
it('should validate figure 2 -- entity statement', () => {
it('should validate figure 2 -- entity statement', () => {
assert.doesNotThrow(() => entityStatementClaimsSchema.parse(entityStatementFigure2))
})

it('should validate figure 3 -- trust mark owners', () => {
it('should validate figure 3 -- trust mark owners', () => {
assert.doesNotThrow(() => trustMarkOwnerSchema.parse(trustMarkOwnersFigure3))
})

it('should validate figure 4 -- trust mark issuers', () => {
it('should validate figure 4 -- trust mark issuers', () => {
assert.doesNotThrow(() => trustMarkIssuerSchema.parse(trustMarkIssuersFigure4))
})

it('should validate figure 7 -- federation entity metadata', () => {
it('should validate figure 7 -- federation entity metadata', () => {
assert.doesNotThrow(() => federationEntityMetadata.schema.parse(federationEntityMetadataFigure7))
})

it('should validate figure 8 -- entity configuration', () => {
assert.doesNotThrow(() => entityConfigurationSchema.parse(entityConfigurationFigure8))
it('should validate figure 8 -- entity configuration', () => {
assert.doesNotThrow(() => entityConfigurationClaimsSchema.parse(entityConfigurationFigure8))
})

it('should validate figure 9 -- entity configuration', () => {
assert.doesNotThrow(() => entityConfigurationSchema.parse(entityConfigurationFigure9))
it('should validate figure 9 -- entity configuration', () => {
assert.doesNotThrow(() => entityConfigurationClaimsSchema.parse(entityConfigurationFigure9))
})

it('should validate figure 12 -- metadata policy', () => {
Expand All @@ -74,7 +74,7 @@ describe('zod validation schemas', () => {
})

it('should validate figure 18 -- entity configuration', () => {
assert.doesNotThrow(() => entityConfigurationSchema.parse(entityConfigurationFigure18))
assert.doesNotThrow(() => entityConfigurationClaimsSchema.parse(entityConfigurationFigure18))
})

it('should validate figure 19 -- trust mark claims', () => {
Expand Down Expand Up @@ -106,31 +106,31 @@ describe('zod validation schemas', () => {
})

it('should validate figure 43 -- entity configuration', () => {
assert.doesNotThrow(() => entityConfigurationSchema.parse(entityConfigurationFigure43))
assert.doesNotThrow(() => entityConfigurationClaimsSchema.parse(entityConfigurationFigure43))
})

it('should validate figure 50 -- entity configuration', () => {
assert.doesNotThrow(() => entityConfigurationSchema.parse(entityConfigurationFigure50))
assert.doesNotThrow(() => entityConfigurationClaimsSchema.parse(entityConfigurationFigure50))
})

it('should validate figure 52 -- entity configuration', () => {
assert.doesNotThrow(() => entityConfigurationSchema.parse(entityConfigurationFigure52))
assert.doesNotThrow(() => entityConfigurationClaimsSchema.parse(entityConfigurationFigure52))
})

it('should validate figure 54 -- entity statement', () => {
assert.doesNotThrow(() => entityStatementClaimsSchema.parse(entityStatementFigure54))
})

it('should validate figure 56 -- entity configuration', () => {
assert.doesNotThrow(() => entityConfigurationSchema.parse(entityConfigurationFigure56))
assert.doesNotThrow(() => entityConfigurationClaimsSchema.parse(entityConfigurationFigure56))
})

it('should validate figure 58 -- entity statement', () => {
assert.doesNotThrow(() => entityStatementClaimsSchema.parse(entityStatementFigure58))
})

it('should validate figure 60 -- entity configutation', () => {
assert.doesNotThrow(() => entityConfigurationSchema.parse(entityConfigurationFigure60))
assert.doesNotThrow(() => entityConfigurationClaimsSchema.parse(entityConfigurationFigure60))
})

it('should validate figure 62 -- entity statement', () => {
Expand All @@ -142,7 +142,7 @@ describe('zod validation schemas', () => {
})

it('should validate figure 69 -- entity configuration', () => {
assert.doesNotThrow(() => entityConfigurationSchema.parse(entityConfigurationFigure69))
assert.doesNotThrow(() => entityConfigurationClaimsSchema.parse(entityConfigurationFigure69))
})

it('should validate figure 70 -- entity statement', () => {
Expand Down
71 changes: 71 additions & 0 deletions packages/core/__tests__/url.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import assert from 'node:assert/strict'
import { describe, it } from 'node:test'
import { addPaths, addSearchParams } from '../src/utils'

const addUrlFixtures = [
{
baseUrl: 'https://example.org',
paths: ['one', 'two'],
expected: 'https://example.org/one/two',
},
{
baseUrl: 'https://example.org/',
paths: ['one', 'two'],
expected: 'https://example.org/one/two',
},
{
baseUrl: 'https://example.org/////',
paths: ['one', 'two///'],
expected: 'https://example.org/one/two',
},
{
baseUrl: 'https://example.org/zero',
paths: ['one', 'two/'],
expected: 'https://example.org/zero/one/two',
},
{
baseUrl: 'https://example.org/zero',
paths: ['/one/', 'two/'],
expected: 'https://example.org/zero/one/two',
},
]

const addSearchParamsFixtures: Array<{
baseUrl: string
searchParams: Record<string, string>
expected: string
}> = [
{
baseUrl: 'https://example.org',
searchParams: { one: 'two' },
expected: 'https://example.org?one=two',
},
{
baseUrl: 'https://example.org?',
searchParams: { one: 'two' },
expected: 'https://example.org?one=two',
},
{
baseUrl: 'https://example.org',
searchParams: { foo: 'bar', baz: 'foo' },
expected: 'https://example.org?foo=bar&baz=foo',
},
]

describe('url parsing', () => {
describe('append path to url', () => {
addUrlFixtures.map(({ paths, expected, baseUrl }) => {
it(`should correctly correctly turn '${baseUrl}' into ${expected}`, () => {
assert.strictEqual(addPaths(baseUrl, ...paths), expected)
})
})
})

describe('append search params to url', () => {
addSearchParamsFixtures.map(({ searchParams, expected, baseUrl }) => {
it(`should correctly correctly turn '${baseUrl}' into ${expected}`, () => {
assert.strictEqual(addSearchParams(baseUrl, searchParams), expected)
})
})
})
})
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
},
"devDependencies": {
"@types/node": "*",
"nock": "14.0.0-beta.7",
"ts-node": "*",
"typescript": "*"
}
Expand Down
9 changes: 0 additions & 9 deletions packages/core/src/entityConfiguration.ts

This file was deleted.

Loading

0 comments on commit a86e90b

Please sign in to comment.