Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/DDO to Gaia-X Service Credential #595

Merged
merged 13 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from 11 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
40 changes: 40 additions & 0 deletions content/asset/form.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
oceanByte marked this conversation as resolved.
Show resolved Hide resolved
"metadata": {
"title": "Metadata",
"fields": [
{
"name": "didweb",
"label": "DID:web",
oceanByte marked this conversation as resolved.
Show resolved Hide resolved
"placeholder": "did:web:delta-dao.com",
oceanByte marked this conversation as resolved.
Show resolved Hide resolved
"required": true
},
{
"name": "credentialHostingPath",
"label": "Credential hosting path",
"placeholder": "https://www.delta-dao.com",
"required": true
},
{
"name": "pathToParticipantCredential",
"label": "Path to the participant credential",
"placeholder": "https://www.delta-dao.com",
"required": true
oceanByte marked this conversation as resolved.
Show resolved Hide resolved
},
{
"name": "knownDependencyCredentials",
"label": "Known dependency credentials",
"placeholder": "https://www.delta-dao.com/.well-known/2210_gx_service_provider_1.json",
"help": "List of known dependency credentials"
},
{
"name": "knownAggregatedServiceCredentials",
"label": "Known aggregated service credentials",
"placeholder": "https://www.delta-dao.com/.well-known/2210_gx_resource_data_road_condition_7.json",
"help": "List of known aggregated service credentials"
}
]
},
"submission": {
"title": "Submit"
}
}
1 change: 1 addition & 0 deletions src/@utils/downloadJSON.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export function isValidJSON(json: string): boolean {
}

// Download JSON file
oceanByte marked this conversation as resolved.
Show resolved Hide resolved

export function downloadJSON(json: string, filename: string): void {
// check if the json is valid
if (isValidJSON(json)) {
oceanByte marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
21 changes: 21 additions & 0 deletions src/components/@shared/DDODownloadButton/_validation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import * as Yup from 'yup'

export const initialValuesAsset = {
didweb: '',
credentialHostingPath: '',
pathToParticipantCredential: '',
knownDependencyCredentials: '',
knownAggregatedServiceCredentials: ''
}

export const validationAsset = Yup.object().shape({
didweb: Yup.string()
.matches(/^did:web:/, 'Invalid DID')
oceanByte marked this conversation as resolved.
Show resolved Hide resolved
.required('Required'),
credentialHostingPath: Yup.string().url('Invalid URL').required('Required'),
pathToParticipantCredential: Yup.string()
.url('Invalid URL')
.required('Required'),
knownDependencyCredentials: Yup.string().url('Invalid URL'),
knownAggregatedServiceCredentials: Yup.string().url('Invalid URL')
oceanByte marked this conversation as resolved.
Show resolved Hide resolved
})
56 changes: 56 additions & 0 deletions src/components/@shared/DDODownloadButton/createJSON.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { downloadJSON } from '@utils/downloadJSON'

export function createServiceCredential(asset, formData) {
oceanByte marked this conversation as resolved.
Show resolved Hide resolved
console.log('formData', formData)
oceanByte marked this conversation as resolved.
Show resolved Hide resolved
const metadata = {
'@context': [
'https://www.w3.org/2018/credentials/v1',
'https://w3id.org/security/suites/jws-2020/v1',
'https://registry.lab.gaia-x.eu/development/api/trusted-shape-registry/v1/shapes/jsonld/trustframework#'
],
type: 'VerifiableCredential',
id: `${formData.credentialHostingPath}/.well-known/2210_gx_service_name.json`,
issuer: formData.didweb,
issuanceDate: '',
credentialSubject: {
id: asset.id,
type: 'gx:ServiceOffering',
'gx:providedBy': {
id: `${formData.pathToParticipantCredential}/.well-known/2210_gx_participant.json`
},
'gx:maintainedBy': {
id: `${formData.pathToParticipantCredential}/.well-known/2210_gx_participant.json`
},
'gx:serviceOffering:serviceModel': 'subscription',
'gx:serviceOffering:subscriptionDuration':
asset.services[0].timeout || 'unlimited',
'gx:policy': `${formData.credentialHostingPath}/yourpolicy.json`,
'gx:termsAndConditions': {
'gx:URL': 'https://portal.pontus-x.eu/terms',
'gx:hash':
'dc6cb5cd5f726e18cf14d9a17fc192a3c5239d7764d6cdb73138a8c53b550dd5f961252c8a0be4b1b8dc42260108dc65e9217053b61fec83634b3e1bb6e6822e'
},
oceanByte marked this conversation as resolved.
Show resolved Hide resolved
'gx:dataAccountExport': {
'gx:requestType': 'email',
'gx:accessType': 'digital',
'gx:formatType': 'mime/json'
},
'gx:serviceOffering:dataProtectionRegime': ['GDPR2016'],
'gx:serviceOffering:gdpr': [
{
'gx:serviceOffering:imprint': `${formData.credentialHostingPath}/imprint/`
},
{
'gx:serviceOffering:privacyPolicy': `${formData.credentialHostingPath}/privacy/`
}
],
'gx:dependsOn': formData.dependencyCredentialsList || [],
'gx:aggregationOf': formData.serviceCredentialList || []
}
}
downloadJSON(
JSON.stringify(metadata),
`service_did_op_${asset.id.split(':')[2]}`
oceanByte marked this conversation as resolved.
Show resolved Hide resolved
)
console.log('metadata', metadata)
oceanByte marked this conversation as resolved.
Show resolved Hide resolved
}
18 changes: 18 additions & 0 deletions src/components/@shared/DDODownloadButton/index.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.credential_margin_bottom {
margin-bottom: calc(var(--spacer) * var(--line-height));
}
.credential_input {
width: 70%;
}

.credential_input_div {
display: flex;
justify-content: space-between;
align-items: flex-end;
flex-wrap: 'wrap';
width: '100%';
oceanByte marked this conversation as resolved.
Show resolved Hide resolved
}

.button {
max-height: 70px;
}
101 changes: 90 additions & 11 deletions src/components/@shared/DDODownloadButton/index.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,99 @@
import { ReactElement } from 'react'
import { ReactElement, useState } from 'react'
import Button from '../atoms/Button'
import { downloadJSON } from '@utils/downloadJSON'
import Modal from '../atoms/Modal'
import Input from '../FormInput'
import { createServiceCredential } from './createJSON'
import { Field, Form, Formik } from 'formik'
import { getFieldContent } from '@utils/form'
import content from '../../../../content/asset/form.json'
import { initialValuesAsset, validationAsset } from './_validation'
import InputWithList from './inputWithList'
import styles from './index.module.css'

export default function DDODownloadButton({
asset
}: {
asset: any
asset: object
}): ReactElement {
const [openModal, setOpenModal] = useState(false)
const [serviceCredentialList, setServiceCredentialList] = useState<
{ id: string }[]
>([])
const [dependencyCredentialsList, setDependencyCredentialsList] = useState<
{ id: string }[]
>([])
const handleSubmit = (values: object) => {
const formData = {
...values,
serviceCredentialList,
dependencyCredentialsList
}
createServiceCredential(asset, formData)
setOpenModal(false)
oceanByte marked this conversation as resolved.
Show resolved Hide resolved
}

return (
<Button
onClick={() =>
downloadJSON(JSON.stringify(asset), `${asset.metadata.name}_metadata`)
}
size="small"
>
Download DDO
</Button>
<>
<Button
className={styles.button}
onClick={() => setOpenModal(true)}
size="small"
>
Prepare Service Credential
</Button>

<Modal
title="Prepare Service Credential"
isOpen={openModal}
onToggleModal={() => {
setOpenModal(false)
setServiceCredentialList([])
setDependencyCredentialsList([])
}}
oceanByte marked this conversation as resolved.
Show resolved Hide resolved
>
<Formik
initialValues={initialValuesAsset}
validationSchema={validationAsset}
onSubmit={(values: object) => {
handleSubmit(values)
}}
oceanByte marked this conversation as resolved.
Show resolved Hide resolved
>
<Form>
<Field
{...getFieldContent('didweb', content.metadata.fields)}
component={Input}
name="didweb"
/>
<Field
{...getFieldContent(
'credentialHostingPath',
content.metadata.fields
)}
component={Input}
name="credentialHostingPath"
/>
<Field
{...getFieldContent(
'pathToParticipantCredential',
content.metadata.fields
)}
component={Input}
name="pathToParticipantCredential"
/>
<InputWithList
fieldname="knownDependencyCredentials"
setList={setDependencyCredentialsList}
list={dependencyCredentialsList}
/>
<InputWithList
fieldname="knownAggregatedServiceCredentials"
setList={setServiceCredentialList}
list={serviceCredentialList}
/>
<Button type="submit">Download</Button>
</Form>
</Formik>
</Modal>
</>
)
}
79 changes: 79 additions & 0 deletions src/components/@shared/DDODownloadButton/inputWithList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { getFieldContent } from '@utils/form'
import content from '../../../../content/asset/form.json'
import { Field, useFormikContext } from 'formik'
import Input from '../FormInput'
import Button from '../atoms/Button'
import { useState } from 'react'
import styles from './index.module.css'
import { ListItem } from '../atoms/Lists'

export default function InputWithList({
fieldname,
setList,
list
}: {
fieldname: string
setList: (list: { id: string }[]) => void
list: { id: string }[]
}) {
const [serviceCredential, setServiceCredential] = useState<string>()
const { setFieldValue, errors } = useFormikContext()
// add credential to the credentiallist
const addCredential = () => {
if (serviceCredential.trim() && !errors[fieldname]) {
setList([...list, { id: serviceCredential }])
setServiceCredential('')
}
}
// delete credential from the list
const deleteCredential = (index: number) => {
list.splice(index, 1)
oceanByte marked this conversation as resolved.
Show resolved Hide resolved
setList([...list])
}
return (
<>
<div className={styles.credential_input_div}>
<div className={`${styles.credential_input}`}>
<Field
{...getFieldContent(fieldname, content.metadata.fields)}
value={serviceCredential}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setServiceCredential((e.target as HTMLInputElement).value)
setFieldValue(fieldname, (e.target as HTMLInputElement).value)
console.log(errors[fieldname])
oceanByte marked this conversation as resolved.
Show resolved Hide resolved
}}
component={Input}
name={fieldname}
/>
</div>

<Button
className={`${styles.credential_margin_bottom}`}
onClick={addCredential}
disabled={errors[fieldname] || !serviceCredential}
>
add
</Button>
</div>
{list.length > 0 && (
<>
<h5 className={`${styles.credential_margin_bottom}`}>
{fieldname === 'knownDependencyCredentials'
? 'Known dependency credentials'
: 'Known aggregated service credentials'}
</h5>
<ul className={`${styles.credential_margin_bottom}`}>
{list.map((item, index) => (
<ListItem key={index}>
<span style={{ marginRight: '10px' }}>{item.id}</span>
<Button style="text" onClick={() => deleteCredential(index)}>
delete
</Button>
</ListItem>
))}
</ul>
</>
)}
</>
)
}
Loading