This repository has been archived by the owner on Aug 25, 2023. It is now read-only.
/
checkAccess.ts
118 lines (109 loc) · 4.65 KB
/
checkAccess.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import { OriginCheckTask, appIsTrustedForMode } from './appIsTrustedForMode'
import { ModesCheckTask, determineAllowedAgentsForModes, AccessModes, AGENT_CLASS_ANYBODY, AGENT_CLASS_ANYBODY_LOGGED_IN } from './determineAllowedAgentsForModes'
import { ACL } from '../rdf/rdf-constants'
import Debug from 'debug'
import { TaskType } from '../api/http/HttpParser'
import { ErrorResult, ResultType } from '../api/http/HttpResponder'
import { StoreManager } from '../rdf/StoreManager'
import { ACL_SUFFIX, AclManager } from './AclManager'
const debug = Debug('checkAccess')
async function modeAllowed (mode: URL, allowedAgentsForModes: AccessModes, webId: URL | undefined, origin: string | undefined, storeManager: StoreManager): Promise<boolean> {
// first check agent:
const agents = (allowedAgentsForModes as any)[mode.toString()]
const webIdAsString: string | undefined = (webId ? webId.toString() : undefined)
debug(mode.toString(), agents, webId ? webId.toString() : undefined)
if ((agents.indexOf(AGENT_CLASS_ANYBODY.toString()) === -1) &&
(agents.indexOf(AGENT_CLASS_ANYBODY_LOGGED_IN.toString()) === -1) &&
(!webIdAsString || agents.indexOf(webIdAsString) === -1)) {
debug('agent check returning false')
return false
}
debug('agent check passed!')
if (!origin) {
debug('no origin header, allowed')
return true
}
// then check origin:
debug('checking origin!')
return appIsTrustedForMode({
origin,
mode,
resourceOwners: allowedAgentsForModes['http://www.w3.org/ns/auth/acl#Control'].map(str => new URL(str))
} as OriginCheckTask, storeManager)
}
export interface AccessCheckTask {
url: URL
webId: URL | undefined
origin: string
requiredAccessModes: Array<URL>
storeManager: StoreManager
}
function urlHasSuffix (url: URL, suffix: string) {
return (url.toString().substr(-suffix.length) === suffix)
}
function removeUrlSuffix (url: URL, suffix: string): URL {
const urlStr = url.toString()
const remainingLength: number = urlStr.length - suffix.length
if (remainingLength < 0) {
throw new Error('no suffix match (URL shorter than suffix)')
}
if (urlStr.substring(remainingLength) !== suffix) {
throw new Error('no suffix match')
}
return new URL(urlStr.substring(0, remainingLength))
}
function urlEquals (one: URL, two: URL) {
return one.toString() === two.toString()
}
export async function checkAccess (task: AccessCheckTask): Promise<boolean> {
debug('AccessCheckTask', task.url.toString(), task.webId ? task.webId.toString() : undefined, task.origin)
debug(task.requiredAccessModes.map(url => url.toString()))
let baseResourceUrl: URL
let resourceIsAclDocument
if (urlHasSuffix(task.url, ACL_SUFFIX)) {
// editing an ACL file requires acl:Control on the base resource
baseResourceUrl = removeUrlSuffix(task.url, ACL_SUFFIX)
resourceIsAclDocument = true
} else {
baseResourceUrl = task.url
resourceIsAclDocument = false
}
const aclManager = new AclManager(task.storeManager)
const { aclGraph, targetUrl, contextUrl } = await aclManager.readAcl(baseResourceUrl)
const resourceIsTarget = urlEquals(baseResourceUrl, targetUrl)
debug('calling allowedAgentsForModes', 'aclGraph', resourceIsTarget, targetUrl.toString(), contextUrl.toString())
const allowedAgentsForModes: AccessModes = await determineAllowedAgentsForModes({
aclGraph,
resourceIsTarget,
targetUrl,
contextUrl
} as ModesCheckTask)
debug('allowedAgentsForModes')
let requiredAccessModes
if (resourceIsAclDocument) {
requiredAccessModes = [ ACL.Control ]
} else {
requiredAccessModes = task.requiredAccessModes
}
let appendOnly = false
// throw if agent or origin does not have access
await Promise.all(requiredAccessModes.map(async (mode: URL) => {
debug('required mode', mode.toString())
if (await modeAllowed(mode, allowedAgentsForModes, task.webId, task.origin, task.storeManager)) {
debug(mode, 'is allowed!')
return
}
debug(`mode ${mode.toString()} is not allowed, but checking for appendOnly now`)
// SPECIAL CASE: append-only
if (mode === ACL.Write && await modeAllowed(ACL.Append, allowedAgentsForModes, task.webId, task.origin, task.storeManager)) {
appendOnly = true
debug('write was requested and is not allowed but append is; setting appendOnly to true')
return
}
debug(`Access denied! ${mode.toString()} access is required for this task, webid is "${task.webId ? task.webId.toString() : undefined}"`)
throw new ErrorResult(ResultType.AccessDenied)
}))
// webId may be reused to check individual ACLs on individual member resources for Glob
// appendOnly may be used to restrict PATCH operations
return appendOnly
}