Skip to content

Commit

Permalink
#611 - Allowed unauthorized ProtocolsQuery to fetch published Protoco…
Browse files Browse the repository at this point in the history
…lsConfigure (#613)

* #611 - Allowed unauthorized ProtocolsQuery to fetch published ProtocolsConfigure
  • Loading branch information
thehenrytsai authored Nov 17, 2023
1 parent 2081a2e commit b6c0605
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 31 deletions.
26 changes: 15 additions & 11 deletions src/handlers/protocols-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { MethodHandler } from '../types/method-handler.js';
import type { ProtocolsConfigureMessage, ProtocolsQueryMessage, ProtocolsQueryReply } from '../types/protocols-types.js';

import { authenticate } from '../core/auth.js';
import { DwnErrorCode } from '../core/dwn-error.js';
import { messageReplyFromError } from '../core/message-reply.js';
import { ProtocolsQuery } from '../interfaces/protocols-query.js';
import { removeUndefinedProperties } from '../utils/object.js';
Expand All @@ -27,21 +28,24 @@ export class ProtocolsQueryHandler implements MethodHandler {
return messageReplyFromError(e, 400);
}

// if this is an anonymous query, query only published ProtocolsConfigures
if (protocolsQuery.author === undefined) {
const entries: ProtocolsConfigureMessage[] = await this.fetchPublishedProtocolsConfigure(tenant, protocolsQuery);
return {
status: { code: 200, detail: 'OK' },
entries
};
}

// authentication & authorization
try {
await authenticate(message.authorization, this.didResolver);
await protocolsQuery.authorize(tenant, this.messageStore);
} catch (e) {
return messageReplyFromError(e, 401);
} catch (error: any) {

// return public ProtocolsConfigures if query fails with a certain authentication or authorization code
if (error.code === DwnErrorCode.AuthenticateJwsMissing || // unauthenticated
error.code === DwnErrorCode.ProtocolsQueryUnauthorized) {

const entries: ProtocolsConfigureMessage[] = await this.fetchPublishedProtocolsConfigure(tenant, protocolsQuery);
return {
status: { code: 200, detail: 'OK' },
entries
};
} else {
return messageReplyFromError(error, 401);
}
}

const query = {
Expand Down
52 changes: 32 additions & 20 deletions tests/handlers/protocols-query.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,16 @@ export function testProtocolsQueryHandler(): void {
});


it('should return published protocols matching the query if query is unauthenticated', async () => {
// scenario: alice has 3 protocols installed: 1 private + 2 published
it('should return published protocols matching the query if query is unauthenticated or unauthorized', async () => {
// scenario:
// 1. Alice has 3 protocols installed: 1 private + 2 published
// 2. Unauthenticated ProtocolsQuery should return published ProtocolsConfigure
// 3. Authenticated ProtocolsQuery by Bob but unauthorized to private ProtocolsConfigures should return published ProtocolsConfigure

const alice = await TestDataGenerator.generatePersona();
const bob = await TestDataGenerator.generatePersona();

// setting up a stub method resolver
TestStubGenerator.stubDidResolver(didResolver, [alice]);
TestStubGenerator.stubDidResolver(didResolver, [alice, bob]);

// insert three messages into DB, two with matching protocol
const protocol1 = await TestDataGenerator.generateProtocolsConfigure({ author: alice, published: false });
Expand All @@ -117,23 +120,45 @@ export function testProtocolsQueryHandler(): void {
});

const conditionalQueryReply = await dwn.processMessage(alice.did, conditionalQuery.message);

expect(conditionalQueryReply.status.code).to.equal(200);
expect(conditionalQueryReply.entries?.length).to.equal(1); // only 1 entry should match the query on protocol

const protocolConfigured = conditionalQueryReply.entries![0] as ProtocolsConfigureMessage;
expect(protocolConfigured).to.deep.equal(protocol2.message);

// testing fetch-all query without filter
// testing authenticated but unauthorized conditional query, it should return only matching published ProtocolsConfigures
const signedConditionalQuery = await ProtocolsQuery.create({
filter : { protocol: protocol2.message.descriptor.definition.protocol },
signer : Jws.createSigner(bob)
});

const signedConditionalQueryReply = await dwn.processMessage(alice.did, signedConditionalQuery.message);
expect(signedConditionalQueryReply.status.code).to.equal(200);
expect(signedConditionalQueryReply.entries?.length).to.equal(1); // only 1 entry should match the query on protocol

const protocolConfigured2 = conditionalQueryReply.entries![0] as ProtocolsConfigureMessage;
expect(protocolConfigured2).to.deep.equal(protocol2.message);

// testing unauthenticated fetch-all query without filter
const fetchAllQuery = await ProtocolsQuery.create({
});

const fetchAllQueryReply = await dwn.processMessage(alice.did, fetchAllQuery.message);

expect(fetchAllQueryReply.status.code).to.equal(200);
expect(fetchAllQueryReply.entries?.length).to.equal(2);
expect(fetchAllQueryReply.entries).to.deep.include(protocol2.message);
expect(fetchAllQueryReply.entries).to.deep.include(protocol3.message);

// testing authenticated but unauthorized fetch-all query without filter, it should return all matching published ProtocolsConfigures
const signedFetchAllQuery = await ProtocolsQuery.create({
signer: Jws.createSigner(bob)
});

const signedFetchAllQueryReply = await dwn.processMessage(alice.did, signedFetchAllQuery.message);
expect(signedFetchAllQueryReply.status.code).to.equal(200);
expect(signedFetchAllQueryReply.entries?.length).to.equal(2);
expect(signedFetchAllQueryReply.entries).to.deep.include(protocol2.message);
expect(signedFetchAllQueryReply.entries).to.deep.include(protocol3.message);
});

it('should return 400 if protocol is not normalized', async () => {
Expand Down Expand Up @@ -190,19 +215,6 @@ export function testProtocolsQueryHandler(): void {
expect(reply.status.detail).to.contain('not a valid DID');
});

it('rejects authenticated non-tenant non-granted ProtocolsConfigures with 401', async () => {
// Bob tries to ProtocolsConfigure to Alice's DWN without a PermissionsGrant
const alice = await DidKeyResolver.generate();
const bob = await DidKeyResolver.generate();

const protocolsQuery = await TestDataGenerator.generateProtocolsQuery({
author: bob,
});
const protocolsQueryReply = await dwn.processMessage(alice.did, protocolsQuery.message);
expect(protocolsQueryReply.status.code).to.equal(401);
expect(protocolsQueryReply.status.detail).to.contain(DwnErrorCode.ProtocolsQueryUnauthorized);
});

describe('Grant authorization', () => {
it('allows an external party to ProtocolsConfigure if they have an active grant', async () => {
// scenario: Alice grants Bob the access to ProtocolsConfigure on her DWN, then Bob does a ProtocolsConfigure
Expand Down

0 comments on commit b6c0605

Please sign in to comment.