Skip to content

Commit 13ab2eb

Browse files
committed
Feat: form-based policy creation
1 parent b030dc6 commit 13ab2eb

File tree

6 files changed

+208
-26
lines changed

6 files changed

+208
-26
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import React, { useRef, useState } from 'react';
2+
3+
function getDateCompatString(date: Date) {
4+
return date.toISOString().split('T')[0]
5+
}
6+
7+
const DatePicker = (props: any) => {
8+
const [date, setDate] = useState(props.value)
9+
const dateInputRef = useRef(null);
10+
11+
const onChange = (e: any) => {
12+
const date = new Date(e.target.value)
13+
setDate(date);
14+
props.onChange(date)
15+
};
16+
17+
return (
18+
<div>
19+
<input
20+
type="date"
21+
onChange={onChange}
22+
value={getDateCompatString(date)}
23+
ref={dateInputRef}
24+
/>
25+
</div>
26+
);
27+
};
28+
export default DatePicker
29+
30+
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import React, { useState, useEffect }from 'react';
2+
import Backdrop from '@mui/material/Backdrop';
3+
import Box from '@mui/material/Box';
4+
import Modal from '@mui/material/Modal';
5+
import Fade from '@mui/material/Fade';
6+
import Button from '@mui/material/Button';
7+
import Typography from '@mui/material/Typography';
8+
import { TextField } from '@mui/material';
9+
import DatePicker from './DatePicker';
10+
import { PolicyFormData, terms } from '../util/PolicyManagement';
11+
12+
const style = {
13+
position: 'absolute' as 'absolute',
14+
top: '50%',
15+
left: '50%',
16+
transform: 'translate(-50%, -50%)',
17+
width: 600,
18+
height: 600,
19+
bgcolor: 'background.paper',
20+
border: '2px solid #000',
21+
boxShadow: 24,
22+
p: 4,
23+
};
24+
25+
const purposeValues: Map<string, string> = new Map([
26+
["age-verification", 'urn:solidlab:uma:claims:purpose:age-verification'],
27+
["some-random-purpose", 'urn:solidlab:uma:claims:purpose:some-random-purpose']
28+
])
29+
30+
const PolicyFormModal = (props: any) => {
31+
const [open, setOpen] = useState(false);
32+
33+
let now = new Date()
34+
let end = new Date()
35+
end.setDate(end.getDate() + 7)
36+
37+
const [target, setTarget] = useState<string>(terms.views.age);
38+
const [assignee, setAssignee] = useState<string>(terms.agents.vendor);
39+
const [startDate, setStartDate] = useState<Date>(now);
40+
const [endDate, setEndDate] = useState<Date>(end);
41+
const [purpose, setPurpose] = useState<string>('urn:solidlab:uma:claims:purpose:age-verification');
42+
const [description, setDescription] = useState<string>('Age verification for food store');
43+
44+
const handleOpen = () => setOpen(true);
45+
const handleClose = () => {
46+
setOpen(false)
47+
};
48+
49+
function commitPolicy(e: any) {
50+
e.preventDefault();
51+
handleClose()
52+
props.addPolicy({target, assignee, startDate, endDate, purpose, description} as PolicyFormData);
53+
}
54+
55+
return (
56+
<div id="addPolicy">
57+
<button id='addPolicyButton' onClick={handleOpen}>Add Policy</button>
58+
{/* <Button onClick={handleOpen}>Open modal</Button> */}
59+
<Modal
60+
aria-labelledby="transition-modal-title"
61+
aria-describedby="transition-modal-description"
62+
open={open}
63+
onClose={handleClose}
64+
closeAfterTransition
65+
slots={{ backdrop: Backdrop }}
66+
slotProps={{
67+
backdrop: {
68+
timeout: 500,
69+
},
70+
}}
71+
>
72+
<Fade in={open}>
73+
<Box sx={style}>
74+
<h1>Add policy</h1>
75+
<form onSubmit={commitPolicy}>
76+
<label key='target'>target: <input value={target} readOnly/></label>
77+
<br />
78+
<label key='assignee'>assignee: <input value={assignee} readOnly/></label>
79+
<br />
80+
<label key='startdate'>start date: <DatePicker value={startDate} onChange={(date: Date) => setStartDate(date)} /></label>
81+
<br />
82+
<label key='enddate'>end date: <DatePicker value={endDate} onChange={(date: Date) => setEndDate(date) } /></label>
83+
<br />
84+
<label key='purpose'>purpose:
85+
<select name='purpose' onChange={(e: any) => setPurpose(e.target.value)}>
86+
{
87+
Array.from(purposeValues.keys()).map(key => (
88+
<option key={key} value={purposeValues.get(key)}>{key}</option>
89+
))
90+
}
91+
</select>
92+
</label>
93+
<br />
94+
<label key='description'>description: <input value={description} onChange={(e:any) => setDescription(e.target.value)}/></label>
95+
96+
<br />
97+
<br />
98+
<input type='submit' />
99+
</form>
100+
</Box>
101+
</Fade>
102+
</Modal>
103+
</div>
104+
);
105+
}
106+
107+
// targetIRI: string,
108+
// requestingPartyIRI: string,
109+
// constraints?: {
110+
// startDate?: Date,
111+
// endDate?: Date,
112+
// purpose?: string
113+
// }
114+
115+
116+
export default PolicyFormModal;

demo/data/demo/public/authorizationsite/src/components/Home.tsx

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React, { useEffect, useState } from "react";
2-
import { createPolicy, doPolicyFlowFromString, readPolicy, readPolicyDirectory } from "../util/PolicyManagement";
2+
import { createAndSubmitPolicy, doPolicyFlowFromString, readPolicy, readPolicyDirectory } from "../util/PolicyManagement";
33
import PolicyModal from "./Modal";
4+
import PolicyFormModal from "./FormModal"
45
import { SimplePolicy } from "../util/policyCreation";
56

67
export default function Home() {
@@ -16,18 +17,26 @@ export default function Home() {
1617
getPolicies()
1718
}, [])
1819

19-
async function addPolicy(policyText: string) {
20+
async function addPolicyFromText(policyText: string) {
2021
console.log('Adding the following policy:')
2122
console.log(policyText)
2223
await doPolicyFlowFromString(policyText)
2324
const policyObject = await readPolicy(policyText)
24-
setPolicyList(policyList.concat(policyObject))
25+
if(policyObject) setPolicyList(policyList.concat(policyObject))
26+
}
27+
28+
async function addPolicyFromFormdata(formdata: any) {
29+
console.log('Adding the following policy:')
30+
console.log(formdata)
31+
const policyObject = await createAndSubmitPolicy(formdata)
32+
if(policyObject) setPolicyList(policyList.concat(policyObject))
2533
}
2634

2735
function renderPolicy(policy: SimplePolicy) {
2836
return (
2937
<div key={policy.policyLocation} className={`policyentry ${policy.policyIRI === selectedPolicy ? 'selectedentry' : ''}`} onClick={() => setSelectedPolicy(policy.policyIRI)}>
30-
<p>{policy.policyIRI}</p>
38+
<p>id: {policy.policyIRI}</p>
39+
<p>{policy.description}</p>
3140
</div>
3241
)
3342
}
@@ -45,7 +54,7 @@ export default function Home() {
4554
policyList.map(renderPolicy)
4655
}
4756
</div>
48-
<PolicyModal addPolicy={addPolicy}/>
57+
<PolicyFormModal addPolicy={addPolicyFromFormdata}/>
4958
</div>
5059
<div id="PolicyDisplayScreen">
5160
<textarea id="policyview" value={selectedPolicyText} readOnly/>

demo/data/demo/public/authorizationsite/src/index.css

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,22 +36,19 @@ nav {
3636

3737

3838
#policypage {
39-
background-color: green;
4039
height: 80vh;
4140
margin-top: 5vh;
4241
}
4342

4443
#policymanagementcontainer {
45-
background-color: red;
44+
background-color: #fffbe4;
4645
height: 200px;
4746
width: 80vw;
4847
margin: auto;
4948
display: flex;
5049
height: 100%;
51-
}
52-
53-
#policyList {
54-
flex: 80%;
50+
overflow: hidden;
51+
border: 1px solid black;
5552
}
5653

5754
#addPolicy {
@@ -69,13 +66,26 @@ nav {
6966
#policyList {
7067
flex: 80%;
7168
overflow-y: scroll;
69+
padding: 30px;
70+
}
71+
72+
::-webkit-scrollbar {
73+
width: 0; /* Remove scrollbar space */
74+
background: transparent; /* Optional: just make scrollbar invisible */
75+
}
76+
/* Optional: show position indicator in red */
77+
::-webkit-scrollbar-thumb {
78+
background: #FF0000;
7279
}
7380

7481
.policyentry {
7582
height: 100px;
76-
border: 1px solid black;
83+
border: 2px solid gray;
7784
margin-top: 10px;
7885
background-color:beige;
86+
text-align: left;
87+
border-radius: 1em;
88+
padding: .5em;
7989
}
8090

8191
.selectedentry {

demo/data/demo/public/authorizationsite/src/util/PolicyManagement.ts

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11
/* eslint-disable max-len */
22

3-
import { Parser, Writer, Store } from 'n3';
3+
import { Parser, Writer, Store, DataFactory } from 'n3';
44
import { SimplePolicy, demoPolicy } from "./policyCreation";
55

6+
export type PolicyFormData = {
7+
target: string,
8+
assignee: string,
9+
startDate: Date,
10+
endDate: Date,
11+
purpose: string,
12+
description: string,
13+
}
14+
615
const parser = new Parser();
716
const writer = new Writer();
817

9-
const terms = {
18+
export const terms = {
1019
solid: {
1120
umaServer: 'http://www.w3.org/ns/solid/terms#umaServer',
1221
viewIndex: 'http://www.w3.org/ns/solid/terms#viewIndex',
@@ -49,57 +58,64 @@ export async function readPolicyDirectory () {
4958
let resourceURIs = resourceQuads.map(q => q.object.value)
5059

5160
console.log('IRIS', responseText, resourceURIs)
52-
const policyObjects = await Promise.all(resourceURIs.map(async (location) => {
61+
let policyObjects = await Promise.all(resourceURIs.map(async (location) => {
5362
const resource = await fetch(location);
5463
const resourceText = await resource.text()
5564
const policy = await readPolicy(resourceText)
5665
return policy
5766
}))
58-
59-
return policyObjects || []
67+
policyObjects = policyObjects.filter(e => e !== null)
68+
return (policyObjects || []) as SimplePolicy[]
6069

6170
}
6271

6372
export async function readPolicy(policyText: string) {
73+
if (policyText === '') return null;
6474
const parsed = await new Parser().parse(policyText)
6575
const store = new Store()
6676
store.addQuads(parsed)
6777
const policyIRI = store.getQuads(null, 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'http://www.w3.org/ns/odrl/2/Agreement', null)[0]?.subject.value
6878
const ruleIRI = store.getQuads(null, 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'http://www.w3.org/ns/odrl/2/Permission', null)[0]?.subject.value
79+
const description = store.getQuads(null, 'http://purl.org/dc/elements/1.1/description', null, null)[0]?.object.value
6980
let simplePolicy: SimplePolicy = {
7081
representation: store,
7182
policyIRI,
7283
ruleIRIs: [ruleIRI],
7384
policyText: policyText,
85+
description,
7486
}
7587

7688
return simplePolicy
7789
}
7890

79-
export async function createPolicy(policyText: string) {
91+
export async function createAndSubmitPolicy(formdata: PolicyFormData) {
8092
console.log('Creating policy')
8193

8294
const policyContainer = 'http://localhost:3000/ruben/settings/policies/';
8395

84-
85-
log(`Having been notified in some way of the access request, Ruben could go to his Authz Companion app, and add a policy allowing the requested access.`);
86-
87-
const startDate = new Date();
88-
const endDate = new Date(startDate.valueOf() + 14 * 24 * 60 * 60 * 1000);
89-
const purpose = 'urn:solidlab:uma:claims:purpose:age-verification'
90-
const policy = demoPolicy(terms.views.age, terms.agents.vendor, { startDate, endDate, purpose })
96+
const policy = demoPolicy(formdata.target, formdata.assignee,
97+
{ startDate: formdata.startDate, endDate: formdata.endDate, purpose: formdata.purpose })
98+
99+
const descriptionQuad = DataFactory.quad(
100+
DataFactory.namedNode(policy.policyIRI),
101+
DataFactory.namedNode('http://purl.org/dc/elements/1.1/description'),
102+
DataFactory.literal(formdata.description)
103+
)
104+
const policyString = writer.quadsToString(policy.representation.getQuads(null, null, null, null).concat(descriptionQuad))
91105

92106
// create container if it does not exist yet
93107
await initContainer(policyContainer)
94108
const policyCreationResponse = await fetch(policyContainer, {
95109
method: 'POST',
96110
headers: { 'content-type': 'text/turtle' },
97-
body: writer.quadsToString(policy.representation.getQuads(null, null, null, null))
111+
body: policyString
98112
});
99113

100114
if (policyCreationResponse.status !== 201) { log('Adding a policy did not succeed...'); throw 0; }
101115

102116
log(`Now that the policy has been set, and the agent has possibly been notified in some way, the agent can try the access request again.`);
117+
policy.policyText = policyString
118+
return policy
103119

104120
}
105121

demo/data/demo/public/authorizationsite/src/util/policyCreation.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export interface SimplePolicy {
1717

1818
policyLocation?: string;
1919
policyText?: string;
20+
description?: string;
2021
}
2122

2223
/**

0 commit comments

Comments
 (0)