Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
198 changes: 196 additions & 2 deletions src/lode/components/AddProperty.vue
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
</p>
<!-- text property input -->
<div
v-if="selectedPropertyToAddIsTextValue || addRelationBy === 'url'"
v-if="(selectedPropertyToAddIsTextValue || addRelationBy === 'url') && !selectedPropertyToAddIsVersionIdentifier"
class="add-property-field">
<!-- if it is a text input type, show the following -->
<div class="add-property__input-type">
Expand Down Expand Up @@ -117,6 +117,113 @@
</div>
</div>
</div>

<!-- Version Identifier input - special handling for complex object -->
<div
v-else-if="selectedPropertyToAddIsVersionIdentifier"
class="add-property-field">
<div class="add-property__input-type">
<div class="add-property__select-type">
<div class="field is-expanded">
<h3 class="subtitle is-6">
{{ editingVersionIdentifierIndex >= 0 ? 'Edit Version Identifier' : 'Enter Version Identifier Information' }}
</h3>

<!-- Display existing version identifiers -->
<div v-if="versionIdentifiers.length > 0"
class="field">
<label class="label">Current Version Identifiers</label>
<div class="box"
v-for="(identifier, index) in versionIdentifiers"
:key="index"
:class="{ 'has-background-info-light': editingVersionIdentifierIndex === index }">
<div class="columns is-vcentered">
<div class="column">
<p><strong>Value:</strong> {{ identifier.identifierValue }}</p>
<p><strong>Name:</strong> {{ identifier.identifierName['@value'] }}</p>
<p><strong>Type:</strong> {{ identifier.identifierType }}</p>
</div>
<div class="column is-narrow">
<div class="buttons">
<button
class="button is-small is-info"
@click="editVersionIdentifier(index)"
:disabled="editingVersionIdentifierIndex >= 0 && editingVersionIdentifierIndex !== index">
{{ editingVersionIdentifierIndex === index ? 'Editing...' : 'Edit' }}
</button>
<button
class="button is-small is-danger"
@click="removeVersionIdentifier(index)"
:disabled="editingVersionIdentifierIndex >= 0">
Remove
</button>
</div>
</div>
</div>
</div>
</div>

<!-- Reused form for both adding and editing -->
<div class="field">
<label class="label">Identifier Value Code</label>
<div class="control">
<input
class="input"
v-model="versionIdentifierData.identifierValue">
</div>
</div>

<div class="field">
<label class="label">Identifier Type Name</label>
<PropertyString
:key="tempKey"
ref="identifierNameInput"
index="null"
expandedProperty="identifierName"
:langString="true"
:range="['http://www.w3.org/2000/01/rdf-schema#langString']"
:newProperty="true"
:profile="profile"
:addSingle="true"
:options="null"
:overrideUpdate="true"
:propertyValue="versionIdentifierData.identifierName"
:customProperty="true"
@updatePropertyString="updateVersionIdentifierName" />
</div>

<div class="field">
<label class="label">Identifier Type</label>
<div class="control">
<input
class="input"
v-model="versionIdentifierData.identifierType">
</div>
</div>

<div class="field is-grouped">
<div class="control">
<button
class="button is-primary"
@click="editingVersionIdentifierIndex >= 0 ? saveVersionIdentifierEdit() : addVersionIdentifier()"
:disabled="!canAddVersionIdentifier">
{{ editingVersionIdentifierIndex >= 0 ? 'Save Changes' : 'Add Version Identifier' }}
</button>
</div>
<div class="control"
v-if="editingVersionIdentifierIndex >= 0">
<button
class="button"
@click="cancelVersionIdentifierEdit()">
Cancel Edit
</button>
</div>
</div>
</div>
</div>
</div>
</div>

<!-- non text value input: create new, search, or url -->
<div
v-else-if="selectedPropertyToAdd !== '' && !selectedPropertyToAddIsTextValue"
Expand Down Expand Up @@ -312,7 +419,15 @@
limitedTypes: [],
limitedConcepts: [],
createNewLevelNameModal: false,
newLevelName: ''
newLevelName: '',
versionIdentifiers: [],
versionIdentifierData: {
identifierValue: '',
identifierName: {},
identifierType: ''
},
tempKey: 0,
editingVersionIdentifierIndex: -1
};
},
mounted: function() {
Expand All @@ -323,6 +438,11 @@
}
},
computed: {
canAddVersionIdentifier() {
return this.versionIdentifierData.identifierValue &&
this.versionIdentifierData.identifierName && this.versionIdentifierData.identifierName['@value'] &&
this.versionIdentifierData.identifierType;
},
queryParams() {
return this.$store.getters['editor/queryParams'];
},
Expand Down Expand Up @@ -428,6 +548,9 @@
if (range.toLowerCase().indexOf("level") !== -1 && this.profile[property]["add"] !== "checkedOptions") {
return false;
}
if (range.toLowerCase().indexOf("https://purl.org/ctdl/terms/IdentifierValue") !== -1) {

Check failure

Code scanning / CodeQL

Incomplete URL substring sanitization High

'
https://purl.org/ctdl/terms/IdentifierValue
' can be anywhere in the URL, and arbitrary hosts may come before or after it.

Copilot Autofix

AI 4 months ago

To fix the issue, the code should parse the range value as a URL and validate its host explicitly. This ensures that the substring check is performed on the correct part of the URL and prevents malicious URLs from bypassing the validation. The URL class in JavaScript can be used to parse the URL and extract its host for comparison.

The fix involves:

  1. Parsing the range value using the URL class.
  2. Validating the host of the parsed URL against a whitelist or ensuring it matches the expected domain.
  3. Replacing the substring check with a more robust host-based validation.
Suggested changeset 1
src/lode/components/AddProperty.vue

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/lode/components/AddProperty.vue b/src/lode/components/AddProperty.vue
--- a/src/lode/components/AddProperty.vue
+++ b/src/lode/components/AddProperty.vue
@@ -550,4 +550,10 @@
             }
-            if (range.toLowerCase().indexOf("https://purl.org/ctdl/terms/IdentifierValue") !== -1) {
-                return false;
+            try {
+                const parsedUrl = new URL(range);
+                const allowedHost = "purl.org";
+                if (parsedUrl.host !== allowedHost || !parsedUrl.pathname.startsWith("/ctdl/terms/IdentifierValue")) {
+                    return false;
+                }
+            } catch (e) {
+                return false; // Invalid URL
             }
EOF
@@ -550,4 +550,10 @@
}
if (range.toLowerCase().indexOf("https://purl.org/ctdl/terms/IdentifierValue") !== -1) {
return false;
try {
const parsedUrl = new URL(range);
const allowedHost = "purl.org";
if (parsedUrl.host !== allowedHost || !parsedUrl.pathname.startsWith("/ctdl/terms/IdentifierValue")) {
return false;
}
} catch (e) {
return false; // Invalid URL
}
Copilot is powered by AI and may make mistakes. Always verify output.
return false;
}
let urlProperties = [
"https://purl.org/ctdlasn/terms/knowledgeEmbodied",
"https://purl.org/ctdlasn/terms/skillEmbodied",
Expand All @@ -444,6 +567,10 @@
return false;
}
return true;
},
selectedPropertyToAddIsVersionIdentifier: function() {
let property = this.selectedPropertyToAdd["value"] ? this.selectedPropertyToAdd["value"] : "";
return property === "https://purl.org/ctdl/terms/versionIdentifier";
}
},
methods: {
Expand Down Expand Up @@ -501,6 +628,73 @@
}
}
});
},
editVersionIdentifier(index) {
this.editingVersionIdentifierIndex = index;
const identifier = this.versionIdentifiers[index];
// Populate the form with existing data
this.versionIdentifierData = {
identifierValue: identifier.identifierValue,
identifierName: {...identifier.identifierName},
identifierType: identifier.identifierType
};
// Force PropertyString component to re-render with new data
this.tempKey++;
},
saveVersionIdentifierEdit() {
if (this.canAddVersionIdentifier && this.editingVersionIdentifierIndex >= 0) {
// Update the existing identifier
this.versionIdentifiers[this.editingVersionIdentifierIndex] = {...this.versionIdentifierData};
this.cancelVersionIdentifierEdit();
}
},
cancelVersionIdentifierEdit() {
this.editingVersionIdentifierIndex = -1;
// Clear the form
this.clearVersionIdentifierForm();
},
// Modify existing addVersionIdentifier to handle edit mode
addVersionIdentifier() {
if (this.canAddVersionIdentifier) {
if (this.editingVersionIdentifierIndex >= 0) {
// This is actually an edit save
this.saveVersionIdentifierEdit();
} else {
// Original add logic
this.versionIdentifiers.push({...this.versionIdentifierData});
this.clearVersionIdentifierForm();
}
}
this.emitVersionIdentifiers();
},
removeVersionIdentifier(index) {
this.versionIdentifiers.splice(index, 1);
this.emitVersionIdentifiers();
},
clearVersionIdentifierForm() {
this.versionIdentifierData = {
identifierValue: '',
identifierName: {},
identifierType: ''
};
this.tempKey = Date.now();
},
updateVersionIdentifierName(value) {
this.versionIdentifierData.identifierName = value;
},
emitVersionIdentifiers() {
const versionId = this.versionIdentifiers.map((x) => {
return {
"https://purl.org/ctdl/terms/identifierValueCode": x.identifierValue,
"https://purl.org/ctdl/terms/identifierTypeName": x.identifierName,
"https://purl.org/ctdl/terms/identifierType": x.identifierType
};
});
// Emit the value through the property-string-updated event
this.updatePropertyString(versionId);
}
},
watch: {
Expand Down
49 changes: 49 additions & 0 deletions src/lode/components/Property.vue
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,49 @@ TO DO MAYBE: Separate out property by editing or not.
</div>
</div>
</template>
<!-- identifier values -->
<div v-else-if="isVersionIdentifier(item)">
<div v-if="editingProperty"
class="columns">
<div class="column">
<div class="field">
<div class="control">
<div>
Identifier Value Code: {{ item['https://purl.org/ctdl/terms/identifierValueCode'][0]['@value'] }}
</div>
<div>
Identifier Type Name: {{ item['https://purl.org/ctdl/terms/identifierTypeName'][0]['@value'] }}
</div>
<div>
Identifier Type: {{ item['https://purl.org/ctdl/terms/identifierType'][0]['@value'] }}
</div>
</div>
</div>
</div>
<div class="column is-narrow">
<div
class="field delete-property-button">
<div class="control">
<label><br></label>
<div
@click="showModal('remove', item)"
class="button is-text has-text-danger">
<i class="fa fa-times" />
</div>
</div>
</div>
</div>
</div>
<div v-else>
<div class="expanded-view-property">
<div :title="item['https://purl.org/ctdl/terms/identifierTypeName'][0]"
class="property">
<span class="tag is-size-7 is-light">Version Identifier</span>
{{ item['https://purl.org/ctdl/terms/identifierTypeName'][0]['@value'] }}
</div>
</div>
</div>
</div>
<!-- non text fields load a component-->
<div
v-else-if="!isText(item)"
Expand Down Expand Up @@ -1050,6 +1093,12 @@ export default {
if (type["@id"] != null && type["@id"] !== undefined) { return true; }
return false;
},
isVersionIdentifier: function(type) {
if (type && type['https://purl.org/ctdl/terms/identifierType']) {
return true;
}
return false;
},
isLink: function(type) {
if (EcObject.keys(type).length === 1) {
if (type["@id"] != null && type["@id"] !== undefined) {
Expand Down
15 changes: 12 additions & 3 deletions src/lode/components/PropertyString.vue
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,9 @@ export default {
view: {
type: String,
default: ''
}
},
overrideUpdate: Boolean, // Emit an event instead if true
customProperty: Boolean
},
components: {
ModalTemplate
Expand All @@ -260,7 +262,7 @@ export default {
data: function() {
var property;
if (this.newProperty === true) {
property = "";
property = this.customProperty ? this.propertyValue : '';
} else {
property = this.expandedThing[this.expandedProperty];
}
Expand Down Expand Up @@ -327,6 +329,9 @@ export default {
this.text = {};
}
}
if (this.customProperty && this.propertyValue) {
this.text = this.propertyValue;
}
},
computed: {
ceasnUser: function() {
Expand Down Expand Up @@ -446,7 +451,11 @@ export default {
},
methods: {
blur: function() {
this.$parent.updatePropertyString(this.text, this.indexInternal);
if (this.overrideUpdate) {
this.$emit('updatePropertyString', this.text, this.indexInternal);
} else {
this.$parent.updatePropertyString(this.text, this.indexInternal);
}
this.isOpen = false;
},
onSearchChange: function() {
Expand Down
Loading
Loading