1 | 'use strict'
|
2 |
|
3 | const debug = require('./../debug').authentication
|
4 |
|
5 | const AuthRequest = require('./auth-request')
|
6 |
|
7 | const url = require('url')
|
8 | const intoStream = require('into-stream')
|
9 |
|
10 | const $rdf = require('rdflib')
|
11 | const ACL = $rdf.Namespace('http://www.w3.org/ns/auth/acl#')
|
12 |
|
13 |
|
14 |
|
15 |
|
16 | class SharingRequest extends AuthRequest {
|
17 | |
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 | constructor (options) {
|
32 | super(options)
|
33 |
|
34 | this.authenticator = options.authenticator
|
35 | this.authMethod = options.authMethod
|
36 | }
|
37 |
|
38 | |
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 | static fromParams (req, res) {
|
49 | let options = AuthRequest.requestOptions(req, res)
|
50 |
|
51 | return new SharingRequest(options)
|
52 | }
|
53 |
|
54 | |
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 | static async get (req, res) {
|
67 | const request = SharingRequest.fromParams(req, res)
|
68 |
|
69 | const appUrl = request.getAppUrl()
|
70 | const appOrigin = appUrl.origin
|
71 | const serverUrl = new url.URL(req.app.locals.ldp.serverUri)
|
72 |
|
73 |
|
74 | if (request.isUserLoggedIn()) {
|
75 | if (
|
76 | !request.isSubdomain(serverUrl.host, new url.URL(request.session.subject._id).host) ||
|
77 | (appUrl && request.isSubdomain(serverUrl.host, appUrl.host) && appUrl.protocol === serverUrl.protocol) ||
|
78 | await request.isAppRegistered(req.app.locals.ldp, appOrigin, request.session.subject._id)
|
79 | ) {
|
80 | request.setUserShared(appOrigin)
|
81 | request.redirectPostSharing()
|
82 | } else {
|
83 | request.renderForm(null, req, appOrigin)
|
84 | }
|
85 | } else {
|
86 | request.redirectPostSharing()
|
87 | }
|
88 | }
|
89 |
|
90 | |
91 |
|
92 |
|
93 |
|
94 |
|
95 |
|
96 |
|
97 |
|
98 |
|
99 | static async share (req, res) {
|
100 | let accessModes = []
|
101 | let consented = false
|
102 | if (req.body) {
|
103 | accessModes = req.body.access_mode || []
|
104 | if (!Array.isArray(accessModes)) {
|
105 | accessModes = [ accessModes ]
|
106 | }
|
107 | consented = req.body.consent
|
108 | }
|
109 |
|
110 | let request = SharingRequest.fromParams(req, res)
|
111 |
|
112 | if (request.isUserLoggedIn()) {
|
113 | const appUrl = request.getAppUrl()
|
114 | const appOrigin = `${appUrl.protocol}//${appUrl.host}`
|
115 | debug('Sharing App')
|
116 |
|
117 | if (consented) {
|
118 | await request.registerApp(req.app.locals.ldp, appOrigin, accessModes, request.session.subject._id)
|
119 | request.setUserShared(appOrigin)
|
120 | }
|
121 |
|
122 |
|
123 | request.redirectPostSharing()
|
124 | } else {
|
125 | request.redirectPostSharing()
|
126 | }
|
127 | }
|
128 |
|
129 | isSubdomain (domain, subdomain) {
|
130 | const domainArr = domain.split('.')
|
131 | const subdomainArr = subdomain.split('.')
|
132 | for (let i = 1; i <= domainArr.length; i++) {
|
133 | if (subdomainArr[subdomainArr.length - i] !== domainArr[domainArr.length - i]) {
|
134 | return false
|
135 | }
|
136 | }
|
137 | return true
|
138 | }
|
139 |
|
140 | setUserShared (appOrigin) {
|
141 | if (!this.session.consentedOrigins) {
|
142 | this.session.consentedOrigins = []
|
143 | }
|
144 | if (!this.session.consentedOrigins.includes(appOrigin)) {
|
145 | this.session.consentedOrigins.push(appOrigin)
|
146 | }
|
147 | }
|
148 |
|
149 | isUserLoggedIn () {
|
150 |
|
151 | return !!(this.session.subject && this.session.subject._id)
|
152 | }
|
153 |
|
154 | getAppUrl () {
|
155 | return new url.URL(this.authQueryParams.redirect_uri)
|
156 | }
|
157 |
|
158 | async getProfileGraph (ldp, webId) {
|
159 | return await new Promise(async (resolve, reject) => {
|
160 | const store = $rdf.graph()
|
161 | const profileText = await ldp.readResource(webId)
|
162 | $rdf.parse(profileText.toString(), store, this.getWebIdFile(webId), 'text/turtle', (error, kb) => {
|
163 | if (error) {
|
164 | reject(error)
|
165 | } else {
|
166 | resolve(kb)
|
167 | }
|
168 | })
|
169 | })
|
170 | }
|
171 |
|
172 | async saveProfileGraph (ldp, store, webId) {
|
173 | const text = $rdf.serialize(undefined, store, this.getWebIdFile(webId), 'text/turtle')
|
174 | await ldp.put(webId, intoStream(text), 'text/turtle')
|
175 | }
|
176 |
|
177 | getWebIdFile (webId) {
|
178 | const webIdurl = new url.URL(webId)
|
179 | return `${webIdurl.origin}${webIdurl.pathname}`
|
180 | }
|
181 |
|
182 | async isAppRegistered (ldp, appOrigin, webId) {
|
183 | const store = await this.getProfileGraph(ldp, webId)
|
184 | return store.each($rdf.sym(webId), ACL('trustedApp')).find((app) => {
|
185 | return store.each(app, ACL('origin')).find(rdfAppOrigin => rdfAppOrigin.value === appOrigin)
|
186 | })
|
187 | }
|
188 |
|
189 | async registerApp (ldp, appOrigin, accessModes, webId) {
|
190 | debug(`Registering app (${appOrigin}) with accessModes ${accessModes} for webId ${webId}`)
|
191 | const store = await this.getProfileGraph(ldp, webId)
|
192 | const origin = $rdf.sym(appOrigin)
|
193 |
|
194 | store.statementsMatching(null, ACL('origin'), origin).forEach(st => {
|
195 | store.removeStatements([...store.statementsMatching(null, ACL('trustedApp'), st.subject)])
|
196 | store.removeStatements([...store.statementsMatching(st.subject)])
|
197 | })
|
198 |
|
199 |
|
200 | const application = new $rdf.BlankNode()
|
201 | store.add($rdf.sym(webId), ACL('trustedApp'), application, webId)
|
202 | store.add(application, ACL('origin'), origin, webId)
|
203 |
|
204 | accessModes.forEach(mode => {
|
205 | store.add(application, ACL('mode'), ACL(mode))
|
206 | })
|
207 | await this.saveProfileGraph(ldp, store, webId)
|
208 | }
|
209 |
|
210 | |
211 |
|
212 |
|
213 |
|
214 |
|
215 |
|
216 |
|
217 |
|
218 |
|
219 | postSharingUrl () {
|
220 | return this.authorizeUrl()
|
221 | }
|
222 |
|
223 | |
224 |
|
225 |
|
226 | redirectPostSharing () {
|
227 | let uri = this.postSharingUrl()
|
228 | debug('Login successful, redirecting to ', uri)
|
229 | this.response.redirect(uri)
|
230 | }
|
231 |
|
232 | |
233 |
|
234 |
|
235 | renderForm (error, req, appOrigin) {
|
236 | let queryString = req && req.url && req.url.replace(/[^?]+\?/, '') || ''
|
237 | let params = Object.assign({}, this.authQueryParams,
|
238 | {
|
239 | registerUrl: this.registerUrl(),
|
240 | returnToUrl: this.returnToUrl,
|
241 | enablePassword: this.localAuth.password,
|
242 | enableTls: this.localAuth.tls,
|
243 | tlsUrl: `/login/tls?${encodeURIComponent(queryString)}`,
|
244 | app_origin: appOrigin
|
245 | })
|
246 |
|
247 | if (error) {
|
248 | params.error = error.message
|
249 | this.response.status(error.statusCode)
|
250 | }
|
251 |
|
252 | this.response.render('auth/sharing', params)
|
253 | }
|
254 | }
|
255 |
|
256 | module.exports = {
|
257 | SharingRequest
|
258 | }
|