Skip to content
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
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "solid-logic",
"version": "6.0.1-0",
"version": "6.0.1-1",
"description": "Core business logic of SolidOS",
"main": "dist/index.cjs.js",
"module": "dist/index.esm.js",
Expand Down
27 changes: 25 additions & 2 deletions src/acl/aclLogic.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { applyPlan as applyAuthorizationPlan, findEffectiveACL, planGrant as planAuthorizationGrant, planPublicRead as planAuthorizationPublicRead, planRevoke as planAuthorizationRevoke, type AccessMode, type AccessSubject, type ACLContext, type Authorization, type PatchPlan } from '@dokieli/web-access-control'
import { applyPlan as applyAuthorizationPlan, Authenticated, findEffectiveACL, planGrant as planAuthorizationGrant, planPublicRead as planAuthorizationPublicRead, planRevoke as planAuthorizationRevoke, Public, type AccessMode, type AccessSubject, type ACLContext, type Authorization, type PatchPlan } from '@dokieli/web-access-control'
import { graph, NamedNode, Namespace, serialize, sym } from 'rdflib'
import type { AclLogic } from '../types'
import { ns as namespace } from '../util/ns'
Expand All @@ -15,8 +15,11 @@ import * as accessControlSubjects from './accessControlSubjects'
// - Terms and constants: ACCESS_MODES, ACL, Authenticated, FOAF, modeFromIRI, modeIRI, namedNode, Public, quad, RDF_TYPE, variable

export const ACCESS_ROLES = ['Owner', 'Editor', 'No Access', 'Viewer', 'Poster', 'Submitter'] as const
export const PUBLIC_ACCESS_ROLES = ['No Access', 'Viewer'] as const
export { Authenticated, Public } from '@dokieli/web-access-control'

export type AccessRole = typeof ACCESS_ROLES[number]
export type PublicAccessRole = typeof PUBLIC_ACCESS_ROLES[number]

export const ACL_LINK = sym(
'http://www.iana.org/assignments/link-relations/acl'
Expand All @@ -33,6 +36,10 @@ export function createAclLogic(store): AclLogic {
{ label: 'Viewer', modes: ['Read'] },
{ label: 'No Access', modes: [] }
]
const PUBLIC_ROLE_RULES: Array<{ label: PublicAccessRole, modes: AccessMode[] }> = [
{ label: 'Viewer', modes: ['Read'] },
{ label: 'No Access', modes: [] }
]

function roleFromModes (modes: Iterable<AccessMode>): AccessRole {
const modeSet = new Set(modes)
Expand All @@ -45,6 +52,17 @@ export function createAclLogic(store): AclLogic {
return [...(ACCESS_ROLE_RULES.find(rule => rule.label === role)?.modes ?? [])]
}

function publicRoleFromModes (modes: Iterable<AccessMode>): PublicAccessRole {
const modeSet = new Set(modes)
const matchingRule = PUBLIC_ROLE_RULES.find(rule => rule.label !== 'No Access' && rule.modes.every(mode => modeSet.has(mode)))

return matchingRule?.label ?? 'No Access'
}

function modesFromPublicRole (role: PublicAccessRole): AccessMode[] {
return [...(PUBLIC_ROLE_RULES.find(rule => rule.label === role)?.modes ?? [])]
}

function getFetch() {
const fetcher = store.fetcher as {
_fetch?: (url: string, init?: RequestInit) => Promise<Response>
Expand Down Expand Up @@ -232,12 +250,17 @@ export function createAclLogic(store): AclLogic {
findAccessGrants,
roleFromModes,
modesFromRole,
publicRoleFromModes,
modesFromPublicRole,
planGrant,
planRevoke,
planPublicRead,
applyPlan,
classifyAccessControlSubject,
setACLUserPublic,
genACLText
genACLText,
Authenticated,
Public,
PUBLIC_ACCESS_ROLES
}
}
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ const authn = solidLogicSingleton.authn
const authSession = solidLogicSingleton.authn.authSession
const store = solidLogicSingleton.store

export { ACL_LINK, ACCESS_ROLES } from './acl/aclLogic'
export type { AccessRole } from './acl/aclLogic'
export { ACL_LINK, ACCESS_ROLES, PUBLIC_ACCESS_ROLES, Authenticated, Public } from './acl/aclLogic'
export type { AccessRole, PublicAccessRole } from './acl/aclLogic'
export { offlineTestID, appContext } from './authn/authUtil'
export { performServerSideLogout } from './authn/serverLogout'
export { reloadOnIdentityReplaced } from './authSession/identityState'
Expand Down
7 changes: 6 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { SessionWithLegacyEvents } from './authSession/authSession'
import type { ACLContext, AccessMode, AccessSubject, Authorization, PatchPlan } from '@dokieli/web-access-control'
import { LiveStore, NamedNode, Statement } from 'rdflib'
import type { AccessControlSubject } from './acl/accessControlSubjects'
import type { AccessRole } from './acl/aclLogic'
import type { AccessRole, PublicAccessRole } from './acl/aclLogic'

export type AppDetails = {
noun: string
Expand Down Expand Up @@ -87,6 +87,8 @@ export interface AclLogic {
classifyAccessControlSubject: (principle: string) => Promise<AccessControlSubject | undefined>,
roleFromModes: (modes: Iterable<AccessMode>) => AccessRole,
modesFromRole: (role: AccessRole) => AccessMode[],
publicRoleFromModes: (modes: Iterable<AccessMode>) => PublicAccessRole,
modesFromPublicRole: (role: PublicAccessRole) => AccessMode[],
planGrant: (resourceURL: string | NamedNode, subject: AccessSubject, modes: AccessMode[]) => Promise<PatchPlan>,
planRevoke: (resourceURL: string | NamedNode, subject: AccessSubject) => Promise<PatchPlan>,
planPublicRead: (resourceURL: string | NamedNode, enabled: boolean) => Promise<PatchPlan>,
Expand All @@ -103,6 +105,9 @@ export interface AclLogic {
public?: []
}
) => string | undefined
Authenticated: AccessSubject
Public: AccessSubject
PUBLIC_ACCESS_ROLES: readonly PublicAccessRole[]
}

export type { ACLContext, AccessMode, AccessSubject, Authorization, PatchPlan }
Expand Down
14 changes: 13 additions & 1 deletion test/publicAclApi.test.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,32 @@
import { describe, expect, expectTypeOf, it } from 'vitest'
import {
ACCESS_ROLES,
Authenticated,
PUBLIC_ACCESS_ROLES,
Public,
solidLogicSingleton,
type ACLContext,
type AccessControlSubjectKind,
type AccessMode,
type AccessRole,
type AccessSubject,
type Authorization,
type PatchPlan
type PatchPlan,
type PublicAccessRole
} from '../src'

describe('public ACL API', () => {
it('exports the role list and exposes mapping through the singleton', () => {
expect(ACCESS_ROLES).toEqual(['Owner', 'Editor', 'No Access', 'Viewer', 'Poster', 'Submitter'])
expect(PUBLIC_ACCESS_ROLES).toEqual(['No Access', 'Viewer'])
expect(Authenticated).toMatchObject({ type: 'agentClass', iri: 'http://www.w3.org/ns/auth/acl#AuthenticatedAgent' })
expect(Public).toMatchObject({ type: 'agentClass', iri: 'http://xmlns.com/foaf/0.1/Agent' })
for (const role of ACCESS_ROLES) {
expect(solidLogicSingleton.acl.roleFromModes(solidLogicSingleton.acl.modesFromRole(role))).toBe(role)
}
for (const role of PUBLIC_ACCESS_ROLES) {
expect(solidLogicSingleton.acl.publicRoleFromModes(solidLogicSingleton.acl.modesFromPublicRole(role))).toBe(role)
}
})

it('exports the types used by access-control consumers', () => {
Expand All @@ -27,6 +37,8 @@ describe('public ACL API', () => {
expectTypeOf(solidLogicSingleton.acl.planGrant).toEqualTypeOf<
(resource: string | import('rdflib').NamedNode, subject: AccessSubject, modes: AccessMode[]) => Promise<PatchPlan>
>()
expectTypeOf(solidLogicSingleton.acl.publicRoleFromModes).toEqualTypeOf<(modes: Iterable<AccessMode>) => PublicAccessRole>()
expectTypeOf(solidLogicSingleton.acl.modesFromPublicRole).toEqualTypeOf<(role: PublicAccessRole) => AccessMode[]>()
expectTypeOf<AccessControlSubjectKind>().toEqualTypeOf<'agent' | 'agentGroup' | 'agentClass' | 'origin'>()
})
})
7 changes: 6 additions & 1 deletion test/resource/resourceLogic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,13 @@ function buildResourceLogic(overrides: {
findAclDocUrl: vi.fn(overrides.findAclDocUrl ?? (async () => undefined)),
roleFromModes: vi.fn(),
modesFromRole: vi.fn(),
publicRoleFromModes: vi.fn(),
modesFromPublicRole: vi.fn(),
setACLUserPublic: vi.fn(),
genACLText: vi.fn()
genACLText: vi.fn(),
Authenticated: { type: 'agentClass', iri: 'http://www.w3.org/ns/auth/acl#AuthenticatedAgent' },
Public: { type: 'agentClass', iri: 'http://xmlns.com/foaf/0.1/Agent' },
PUBLIC_ACCESS_ROLES: ['No Access', 'Viewer']
} as any,
{
createContainer: vi.fn(async () => undefined),
Expand Down
7 changes: 6 additions & 1 deletion test/resourceLogic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,17 @@ describe('resourceLogic', () => {
classifyAccessControlSubject: vi.fn().mockResolvedValue(undefined),
roleFromModes: vi.fn(),
modesFromRole: vi.fn(),
publicRoleFromModes: vi.fn(),
modesFromPublicRole: vi.fn(),
planGrant: vi.fn(),
planRevoke: vi.fn(),
planPublicRead: vi.fn(),
applyPlan: vi.fn(),
setACLUserPublic: vi.fn(),
genACLText: vi.fn()
genACLText: vi.fn(),
Authenticated: { type: 'agentClass', iri: 'http://www.w3.org/ns/auth/acl#AuthenticatedAgent' },
Public: { type: 'agentClass', iri: 'http://xmlns.com/foaf/0.1/Agent' },
PUBLIC_ACCESS_ROLES: ['No Access', 'Viewer']
}
containerLogic = {
createContainer: vi.fn(),
Expand Down
Loading