Skip to content

Commit 4d36e12

Browse files
Merge pull request #1210 from solid/sharingPatch
Sharing patch
2 parents bdc5acb + f21df34 commit 4d36e12

File tree

15 files changed

+478
-242
lines changed

15 files changed

+478
-242
lines changed

common/disclaimer.html

Lines changed: 0 additions & 44 deletions
This file was deleted.

default-views/auth/consent.hbs

Lines changed: 0 additions & 48 deletions
This file was deleted.

default-views/auth/sharing.hbs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1">
6+
<title>{{title}}</title>
7+
<!-- Bootstrap CSS and Theme for demo purposes -->
8+
<link rel="stylesheet" href="/common/css/bootstrap.min.css">
9+
<link rel="stylesheet" href="/common/css/solid.css">
10+
</head>
11+
<body>
12+
<div class="container title">
13+
<h1>Authorize {{app_origin}} to access your Pod?</h1>
14+
<p>Solid allows you to precisely choose what other people and apps can read and write in a Pod. This version of the authorization user interface (node-solid-server V5.1) only supports the toggle of global access permissions to all of the data in your Pod.</p>
15+
<p><strong>If you don’t want to set these permissions at a global level, uncheck all of the boxes below, then click authorize.</strong> This will add the application origin to your authorization list, without granting it permission to any of your data yet. You will then need to manage those permissions yourself by setting them explicitly in the places you want this application to access.</p>
16+
<div class="panel panel-default">
17+
<div class="panel-body">
18+
<div class="page-title">
19+
<p>By clicking Authorize, any app from {{app_origin}} will be able to:</p>
20+
</div>
21+
<form method="post" action="/sharing">
22+
23+
<input id="read" type="checkbox" name="access_mode" value="Read" checked>
24+
<label for="read">Read all documents in the Pod</label>
25+
<br>
26+
27+
<input id="write" type="checkbox" name="access_mode" value="Write" checked>
28+
<label for="write">Add data to existing documents, and create new documents</label>
29+
<br>
30+
31+
<input id="append" type="checkbox" name="access_mode" value="Append" checked>
32+
<label for="append">Modify and delete data in existing documents, and delete documents</label>
33+
<br>
34+
35+
<input id="control" type="checkbox" name="access_mode" value="Control">
36+
<label for="control">Give other people and apps access to the Pod, or revoke their (and your) access</label>
37+
<br>
38+
<br>
39+
40+
<button type="submit" class="btn btn-primary" name="consent" value="true">Authorize</button>
41+
<button type="submit" class="btn btn-default" name="cancel" value="true">Cancel</button>
42+
{{> auth/auth-hidden-fields}}
43+
</form>
44+
</div>
45+
</div>
46+
<p><i>This server (node-solid-server V5.1) only implements a limited subset of OpenID Connect, and doesn’t yet support token issuance for applications. OIDC Token Issuance and fine-grained management through this authorization user interface is currently in the development backlog for node-solid-server</i></p>
47+
</div>
48+
</body>
49+
</html>

lib/api/authn/webid-oidc.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const { routeResolvedFile } = require('../../utils')
88
const bodyParser = require('body-parser').urlencoded({ extended: false })
99
const OidcManager = require('../../models/oidc-manager')
1010
const { LoginRequest } = require('../../requests/login-request')
11-
const { ConsentRequest } = require('../../requests/consent-request')
11+
const { SharingRequest } = require('../../requests/sharing-request')
1212

1313
const restrictToTopDomain = require('../../handlers/restrict-to-top-domain')
1414

@@ -84,8 +84,8 @@ function middleware (oidc) {
8484

8585
router.post('/login/tls', bodyParser, LoginRequest.loginTls)
8686

87-
router.get('/consent', ConsentRequest.get)
88-
router.post('/consent', bodyParser, ConsentRequest.giveConsent)
87+
router.get('/sharing', SharingRequest.get)
88+
router.post('/sharing', bodyParser, SharingRequest.share)
8989

9090
router.get('/account/password/reset', restrictToTopDomain, PasswordResetEmailRequest.get)
9191
router.post('/account/password/reset', restrictToTopDomain, bodyParser, PasswordResetEmailRequest.post)

lib/requests/auth-request.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -218,13 +218,13 @@ class AuthRequest {
218218
return url.format(signupUrl)
219219
}
220220

221-
consentUrl () {
221+
sharingUrl () {
222222
let host = this.accountManager.host
223-
let consentUrl = url.parse(url.resolve(host.serverUri, '/consent'))
223+
let sharingUrl = url.parse(url.resolve(host.serverUri, '/sharing'))
224224

225-
consentUrl.query = this.authQueryParams
225+
sharingUrl.query = this.authQueryParams
226226

227-
return url.format(consentUrl)
227+
return url.format(sharingUrl)
228228
}
229229
}
230230

lib/requests/login-request.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ class LoginRequest extends AuthRequest {
159159
postLoginUrl (validUser) {
160160
// Login request is part of an app's auth flow
161161
if (/token|code/.test(this.authQueryParams['response_type'])) {
162-
return this.consentUrl()
162+
return this.sharingUrl()
163163
// Login request is a user going to /login in browser
164164
} else if (validUser) {
165165
return this.authQueryParams['redirect_uri'] || validUser.accountUri
Lines changed: 55 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const ACL = $rdf.Namespace('http://www.w3.org/ns/auth/acl#')
1313
/**
1414
* Models a local Login request
1515
*/
16-
class ConsentRequest extends AuthRequest {
16+
class SharingRequest extends AuthRequest {
1717
/**
1818
* @constructor
1919
* @param options {Object}
@@ -48,7 +48,7 @@ class ConsentRequest extends AuthRequest {
4848
static fromParams (req, res) {
4949
let options = AuthRequest.requestOptions(req, res)
5050

51-
return new ConsentRequest(options)
51+
return new SharingRequest(options)
5252
}
5353

5454
/**
@@ -64,19 +64,23 @@ class ConsentRequest extends AuthRequest {
6464
* @param res {ServerResponse}
6565
*/
6666
static async get (req, res) {
67-
const request = ConsentRequest.fromParams(req, res)
67+
const request = SharingRequest.fromParams(req, res)
6868

69-
const appOrigin = request.getAppOrigin()
70-
// Check if is already registered or is data browser
69+
const appUrl = request.getAppUrl()
70+
const appOrigin = appUrl.origin
71+
const serverUrl = new url.URL(req.app.locals.ldp.serverUri)
72+
73+
// Check if is already registered or is data browser or the webId is not on this machine
7174
if (request.isUserLoggedIn()) {
7275
if (
73-
appOrigin === req.app.locals.ldp.serverUri ||
76+
new url.URL(request.session.subject._id).origin !== serverUrl.origin ||
77+
(appUrl && request.isSubdomain(serverUrl.host, appUrl.host) && appUrl.protocol === serverUrl.protocol) ||
7478
await request.isAppRegistered(req.app.locals.ldp, appOrigin, request.session.subject._id)
7579
) {
76-
request.setUserConsent(appOrigin)
77-
request.redirectPostConsent()
80+
request.setUserShared(appOrigin)
81+
request.redirectPostSharing()
7882
} else {
79-
request.renderForm(null, req)
83+
request.renderForm(null, req, appOrigin)
8084
}
8185
}
8286
}
@@ -90,31 +94,46 @@ class ConsentRequest extends AuthRequest {
9094
*
9195
* @return {Promise}
9296
*/
93-
static async giveConsent (req, res) {
97+
static async share (req, res) {
9498
let accessModes = []
9599
let consented = false
96100
if (req.body) {
97-
accessModes = req.body.access_mode
101+
accessModes = req.body.access_mode || []
102+
if (!Array.isArray(accessModes)) {
103+
accessModes = [ accessModes ]
104+
}
98105
consented = req.body.consent
99106
}
100107

101-
let request = ConsentRequest.fromParams(req, res)
108+
let request = SharingRequest.fromParams(req, res)
102109

103110
if (request.isUserLoggedIn()) {
104-
const appOrigin = request.getAppOrigin()
105-
debug('Providing consent for app sharing')
111+
const appUrl = request.getAppUrl()
112+
const appOrigin = `${appUrl.protocol}//${appUrl.host}`
113+
debug('Sharing App')
106114

107115
if (consented) {
108116
await request.registerApp(req.app.locals.ldp, appOrigin, accessModes, request.session.subject._id)
109-
request.setUserConsent(appOrigin)
117+
request.setUserShared(appOrigin)
110118
}
111119

112120
// Redirect once that's all done
113-
request.redirectPostConsent()
121+
request.redirectPostSharing()
122+
}
123+
}
124+
125+
isSubdomain (domain, subdomain) {
126+
const domainArr = domain.split('.')
127+
const subdomainArr = subdomain.split('.')
128+
for (let i = 1; i <= domainArr.length; i++) {
129+
if (subdomainArr[subdomainArr.length - i] !== domainArr[domainArr.length - i]) {
130+
return false
131+
}
114132
}
133+
return true
115134
}
116135

117-
setUserConsent (appOrigin) {
136+
setUserShared (appOrigin) {
118137
if (!this.session.consentedOrigins) {
119138
this.session.consentedOrigins = []
120139
}
@@ -133,16 +152,15 @@ class ConsentRequest extends AuthRequest {
133152
return true
134153
}
135154

136-
getAppOrigin () {
137-
const parsed = url.parse(this.authQueryParams.redirect_uri)
138-
return `${parsed.protocol}//${parsed.host}`
155+
getAppUrl () {
156+
return new url.URL(this.authQueryParams.redirect_uri)
139157
}
140158

141159
async getProfileGraph (ldp, webId) {
142160
return await new Promise(async (resolve, reject) => {
143161
const store = $rdf.graph()
144162
const profileText = await ldp.readResource(webId)
145-
$rdf.parse(profileText.toString(), store, 'https://localhost:8443/profile/card', 'text/turtle', (error, kb) => {
163+
$rdf.parse(profileText.toString(), store, this.getWebIdFile(webId), 'text/turtle', (error, kb) => {
146164
if (error) {
147165
reject(error)
148166
} else {
@@ -153,10 +171,15 @@ class ConsentRequest extends AuthRequest {
153171
}
154172

155173
async saveProfileGraph (ldp, store, webId) {
156-
const text = $rdf.serialize(undefined, store, webId, 'text/turtle')
174+
const text = $rdf.serialize(undefined, store, this.getWebIdFile(webId), 'text/turtle')
157175
await ldp.put(webId, intoStream(text), 'text/turtle')
158176
}
159177

178+
getWebIdFile (webId) {
179+
const webIdurl = new url.URL(webId)
180+
return `${webIdurl.origin}${webIdurl.path}`
181+
}
182+
160183
async isAppRegistered (ldp, appOrigin, webId) {
161184
const store = await this.getProfileGraph(ldp, webId)
162185
return store.each($rdf.sym(webId), ACL('trustedApp')).find((app) => {
@@ -165,6 +188,7 @@ class ConsentRequest extends AuthRequest {
165188
}
166189

167190
async registerApp (ldp, appOrigin, accessModes, webId) {
191+
debug(`Registering app (${appOrigin}) with accessModes ${accessModes} for webId ${webId}`)
168192
const store = await this.getProfileGraph(ldp, webId)
169193
const origin = $rdf.sym(appOrigin)
170194
// remove existing statements on same origin - if it exists
@@ -177,6 +201,7 @@ class ConsentRequest extends AuthRequest {
177201
const application = new $rdf.BlankNode()
178202
store.add($rdf.sym(webId), ACL('trustedApp'), application, webId)
179203
store.add(application, ACL('origin'), origin, webId)
204+
180205
accessModes.forEach(mode => {
181206
store.add(application, ACL('mode'), ACL(mode))
182207
})
@@ -192,42 +217,43 @@ class ConsentRequest extends AuthRequest {
192217
*
193218
* @return {string}
194219
*/
195-
postConsentUrl () {
220+
postSharingUrl () {
196221
return this.authorizeUrl()
197222
}
198223

199224
/**
200225
* Redirects the Login request to continue on the OIDC auth workflow.
201226
*/
202-
redirectPostConsent () {
203-
let uri = this.postConsentUrl()
227+
redirectPostSharing () {
228+
let uri = this.postSharingUrl()
204229
debug('Login successful, redirecting to ', uri)
205230
this.response.redirect(uri)
206231
}
207232

208233
/**
209234
* Renders the login form
210235
*/
211-
renderForm (error, req) {
236+
renderForm (error, req, appOrigin) {
212237
let queryString = req && req.url && req.url.replace(/[^?]+\?/, '') || ''
213238
let params = Object.assign({}, this.authQueryParams,
214239
{
215240
registerUrl: this.registerUrl(),
216241
returnToUrl: this.returnToUrl,
217242
enablePassword: this.localAuth.password,
218243
enableTls: this.localAuth.tls,
219-
tlsUrl: `/login/tls?${encodeURIComponent(queryString)}`
244+
tlsUrl: `/login/tls?${encodeURIComponent(queryString)}`,
245+
app_origin: appOrigin
220246
})
221247

222248
if (error) {
223249
params.error = error.message
224250
this.response.status(error.statusCode)
225251
}
226252

227-
this.response.render('auth/consent', params)
253+
this.response.render('auth/sharing', params)
228254
}
229255
}
230256

231257
module.exports = {
232-
ConsentRequest
258+
SharingRequest
233259
}

0 commit comments

Comments
 (0)