Skip to content

Commit fa9be4b

Browse files
authored
Secure repository file paths and stream downloads (#1024)
* Secure repository file paths and stream downloads Centralize repository file handling through FileRepositoryHelper for AAS thumbnails, submodel attachments, and AASX packages. Uploaded logical filenames are now validated as display names only, while stored repository ids are generated server-side. Stream thumbnail and attachment HTTP responses from the repository instead of materializing repository ids as local filesystem paths. Keep legacy File APIs compatible via safe temp-file/default fallbacks. Update replacement/delete flows to update domain references safely and clean up old repository entries after successful updates. Add regression coverage for path traversal names, absolute repository keys, stream handling, and authorization behavior. * Fixes failing tests
1 parent d42fbd0 commit fa9be4b

44 files changed

Lines changed: 1674 additions & 275 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

basyx.aasenvironment/basyx.aasenvironment-core/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/base/DefaultAASEnvironment.java

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,9 @@ private void handleFileError(File file) {
371371

372372

373373
private byte[] getFileContent(String submodelId, File file) throws IOException {
374-
return submodelRepository.getFileByFilePath(submodelId, file.getValue()).readAllBytes();
374+
try (InputStream fileContent = submodelRepository.getFileByFilePath(submodelId, file.getValue())) {
375+
return fileContent.readAllBytes();
376+
}
375377
}
376378

377379
private boolean isFileAlreadyAdded(String filePath){
@@ -386,7 +388,7 @@ private void addThumbnailToRelatedFiles(String aasId, Resource thumbnail, List<I
386388
String newPath = getThumbnailPathInAASX(thumbnail.getPath());
387389
relatedFiles.add(
388390
new InMemoryFile(
389-
Files.readAllBytes(aasRepository.getThumbnail(aasId).toPath()),
391+
getThumbnailContent(aasId),
390392
newPath
391393
)
392394
);
@@ -397,6 +399,25 @@ private void addThumbnailToRelatedFiles(String aasId, Resource thumbnail, List<I
397399
}
398400
}
399401

402+
private byte[] getThumbnailContent(String aasId) throws IOException {
403+
try (InputStream thumbnailContent = aasRepository.getThumbnailInputStream(aasId)) {
404+
return thumbnailContent.readAllBytes();
405+
} catch (UnsupportedOperationException e) {
406+
return getThumbnailContentFromFile(aasId);
407+
}
408+
}
409+
410+
private byte[] getThumbnailContentFromFile(String aasId) throws IOException {
411+
java.io.File thumbnailFile = aasRepository.getThumbnail(aasId);
412+
413+
try {
414+
return Files.readAllBytes(thumbnailFile.toPath());
415+
} finally {
416+
if (thumbnailFile != null)
417+
thumbnailFile.delete();
418+
}
419+
}
420+
400421
private static boolean isThumbnailSet(Resource thumbnail) {
401422
return thumbnail != null && thumbnail.getPath() != null;
402423
}

basyx.aasrepository/basyx.aasrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/CrudAasRepository.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,11 @@ public File getThumbnail(String aasId) {
138138
return getService(aasId).getThumbnail();
139139
}
140140

141+
@Override
142+
public InputStream getThumbnailInputStream(String aasId) {
143+
return getService(aasId).getThumbnailInputStream();
144+
}
145+
141146
@Override
142147
public void setThumbnail(String aasId, String fileName, String contentType, InputStream inputStream) {
143148
getService(aasId).setThumbnail(fileName, contentType, inputStream);

basyx.aasrepository/basyx.aasrepository-client/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/client/ConnectedAasRepository.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,11 @@ public File getThumbnail(String aasId) {
181181
return getConnectedAasService(aasId).getThumbnail();
182182
}
183183

184+
@Override
185+
public InputStream getThumbnailInputStream(String aasId) {
186+
return getConnectedAasService(aasId).getThumbnailInputStream();
187+
}
188+
184189
@Override
185190
public void setThumbnail(String aasId, String fileName, String contentType, InputStream inputStream) {
186191
getConnectedAasService(aasId).setThumbnail(fileName, contentType, inputStream);
Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,32 @@
11
/*******************************************************************************
22
* Copyright (C) 2023 the Eclipse BaSyx Authors
3-
*
3+
*
44
* Permission is hereby granted, free of charge, to any person obtaining
55
* a copy of this software and associated documentation files (the
66
* "Software"), to deal in the Software without restriction, including
77
* without limitation the rights to use, copy, modify, merge, publish,
88
* distribute, sublicense, and/or sell copies of the Software, and to
99
* permit persons to whom the Software is furnished to do so, subject to
1010
* the following conditions:
11-
*
11+
*
1212
* The above copyright notice and this permission notice shall be
1313
* included in all copies or substantial portions of the Software.
14-
*
14+
*
1515
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
1616
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1717
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
1818
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
1919
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
2020
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
2121
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22-
*
22+
*
2323
* SPDX-License-Identifier: MIT
2424
******************************************************************************/
2525
package org.eclipse.digitaltwin.basyx.aasrepository;
2626

2727
import java.io.File;
28+
import java.io.FileInputStream;
29+
import java.io.FileNotFoundException;
2830
import java.io.InputStream;
2931
import java.util.List;
3032

@@ -34,28 +36,29 @@
3436
import org.eclipse.digitaltwin.aas4j.v3.model.SpecificAssetId;
3537
import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingIdentifierException;
3638
import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException;
39+
import org.eclipse.digitaltwin.basyx.core.exceptions.FileHandlingException;
3740
import org.eclipse.digitaltwin.basyx.core.exceptions.MissingIdentifierException;
3841
import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult;
3942
import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo;
4043

4144
/**
4245
* Specifies the overall AasRepository API
43-
*
46+
*
4447
* @author schnicke, kammognie
4548
*
4649
*/
4750
public interface AasRepository {
48-
51+
4952
/**
5053
* Retrieves all Asset Administration Shells from the repository
51-
*
54+
*
5255
* @return a list of all found Asset Administration Shells
5356
*/
5457
public CursorResult<List<AssetAdministrationShell>> getAllAas(List<SpecificAssetId> assetIds, String idShort, PaginationInfo pInfo);
5558

5659
/**
5760
* Retrieves a specific AAS
58-
*
61+
*
5962
* @param aasId
6063
* the id of the AAS
6164
* @return the requested AAS
@@ -64,7 +67,7 @@ public interface AasRepository {
6467

6568
/**
6669
* Creates a new AAS at the endpoint
67-
*
70+
*
6871
* @param aas
6972
* the AAS to be created
7073
* @throws MissingIdentifierException
@@ -74,22 +77,22 @@ public interface AasRepository {
7477

7578
/**
7679
* Deletes a specific AAS
77-
*
80+
*
7881
* @param aasId
7982
* the id of the AAS to be deleted
8083
*/
8184
public void deleteAas(String aasId);
8285

8386
/**
8487
* Overwrites an existing AAS
85-
*
88+
*
8689
* @param aas
8790
*/
8891
public void updateAas(String aasId, AssetAdministrationShell aas);
8992

9093
/**
9194
* Returns a List of References to Submodels
92-
*
95+
*
9396
* @param aasId
9497
* @param pInfo
9598
* @return
@@ -98,48 +101,67 @@ public interface AasRepository {
98101

99102
/**
100103
* Adds a Submodel Reference
101-
*
104+
*
102105
* @param submodelReference
103106
*/
104107
public void addSubmodelReference(String aasId, Reference submodelReference);
105108

106109
/**
107110
* Removes a Submodel Reference
108-
*
111+
*
109112
* @param submodelId
110113
*/
111114
public void removeSubmodelReference(String aasId, String submodelId);
112115

113116
/**
114117
* Sets the asset-information of a specific AAS
115-
*
118+
*
116119
* @param aasId
117120
* the id of the AAS
118121
*/
119122
public void setAssetInformation(String aasId, AssetInformation aasInfo) throws ElementDoesNotExistException;
120123

121124
/**
122125
* Retrieves the asset-information of a specific AAS
123-
*
126+
*
124127
* @param aasId
125128
* the id of the AAS
126-
*
129+
*
127130
* @return the requested AAS
128131
*/
129132
public AssetInformation getAssetInformation(String aasId) throws ElementDoesNotExistException;
130133

131134
/**
132135
* Get Thumbnail of the specific aas
133-
*
136+
*
134137
* @param aasId
135138
* the id of the AAS
136139
* @return the file of the thumbnail
137140
*/
138141
public File getThumbnail(String aasId);
139142

143+
/**
144+
* Get Thumbnail content of the specific aas as a stream
145+
* <p>
146+
* The caller is responsible for closing the returned stream.
147+
*
148+
* @param aasId
149+
* the id of the AAS
150+
* @return the stream of the thumbnail
151+
* @throws org.eclipse.digitaltwin.basyx.core.exceptions.FileDoesNotExistException
152+
* if no thumbnail is stored
153+
*/
154+
public default InputStream getThumbnailInputStream(String aasId) {
155+
try {
156+
return new FileInputStream(getThumbnail(aasId));
157+
} catch (FileNotFoundException e) {
158+
throw new FileHandlingException("Could not open thumbnail stream.", e);
159+
}
160+
}
161+
140162
/**
141163
* Set Thumbnail of the AAS
142-
*
164+
*
143165
* @param aasId
144166
* the id of the AAS
145167
* @param fileName
@@ -153,19 +175,19 @@ public interface AasRepository {
153175

154176
/**
155177
* Delete the thumbnail file of the AAS
156-
*
178+
*
157179
* @param aasId
158180
* the id of the AAS
159181
*/
160182
public void deleteThumbnail(String aasId);
161-
183+
162184
/**
163185
* Returns the name of the repository
164-
*
186+
*
165187
* @return repoName
166188
*/
167189
public default String getName() {
168190
return "aas-repo";
169191
}
170-
192+
171193
}

basyx.aasrepository/basyx.aasrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/AasRepositoryAasServiceWrapper.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ public File getThumbnail() {
8888
return repoApi.getThumbnail(aasId);
8989
}
9090

91+
@Override
92+
public InputStream getThumbnailInputStream() {
93+
return repoApi.getThumbnailInputStream(aasId);
94+
}
95+
9196
@Override
9297
public void setThumbnail(String fileName, String contentType, InputStream inputStream) {
9398
repoApi.setThumbnail(aasId, fileName, contentType, inputStream);

0 commit comments

Comments
 (0)