001/* 002 * Licensed to DuraSpace under one or more contributor license agreements. 003 * See the NOTICE file distributed with this work for additional information 004 * regarding copyright ownership. 005 * 006 * DuraSpace licenses this file to you under the Apache License, 007 * Version 2.0 (the "License"); you may not use this file except in 008 * compliance with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.fcrepo.integration.http.api; 019 020import static javax.ws.rs.core.Response.Status.METHOD_NOT_ALLOWED; 021import static javax.ws.rs.core.Response.Status.OK; 022import static org.apache.jena.graph.Node.ANY; 023import static org.apache.jena.graph.NodeFactory.createLiteral; 024import static javax.ws.rs.core.Response.Status.CREATED; 025 026import static org.fcrepo.kernel.api.FedoraTypes.FCR_FIXITY; 027import static org.fcrepo.kernel.api.RdfLexicon.HAS_FIXITY_RESULT; 028import static org.fcrepo.kernel.api.RdfLexicon.HAS_FIXITY_STATE; 029import static org.fcrepo.kernel.api.RdfLexicon.HAS_MESSAGE_DIGEST; 030import static org.fcrepo.kernel.api.RdfLexicon.HAS_SIZE; 031import static org.junit.Assert.assertEquals; 032import static org.junit.Assert.assertNotNull; 033import static org.junit.Assert.assertTrue; 034 035import java.io.IOException; 036import java.nio.charset.StandardCharsets; 037import java.nio.file.Files; 038import java.nio.file.StandardOpenOption; 039import java.util.Iterator; 040import java.util.Map; 041import java.util.stream.Collectors; 042 043import edu.wisc.library.ocfl.api.OcflRepository; 044import edu.wisc.library.ocfl.api.model.ObjectVersionId; 045import org.apache.http.client.methods.CloseableHttpResponse; 046import org.apache.http.client.methods.HttpGet; 047 048import org.apache.http.client.methods.HttpPost; 049import org.apache.http.client.methods.HttpPut; 050import org.apache.jena.sparql.core.Quad; 051import org.fcrepo.config.OcflPropsConfig; 052import org.fcrepo.http.commons.test.util.CloseableDataset; 053 054import org.fcrepo.kernel.api.identifiers.FedoraId; 055import org.junit.Before; 056import org.junit.Test; 057 058import org.apache.jena.datatypes.RDFDatatype; 059import org.apache.jena.datatypes.TypeMapper; 060import org.apache.jena.sparql.core.DatasetGraph; 061import org.springframework.test.context.TestExecutionListeners; 062 063/** 064 * <p>FedoraFixityIT class.</p> 065 * 066 * @author awoods 067 * @author ajs6f 068 */ 069@TestExecutionListeners( 070 listeners = { TestIsolationExecutionListener.class }, 071 mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS) 072public class FedoraFixityIT extends AbstractResourceIT { 073 074 private static final RDFDatatype IntegerType = TypeMapper.getInstance().getTypeByClass(Integer.class); 075 076 private OcflPropsConfig ocflConfig; 077 private OcflRepository ocflRepo; 078 079 @Before 080 public void setup() { 081 ocflConfig = getBean(OcflPropsConfig.class); 082 ocflRepo = getBean(OcflRepository.class); 083 } 084 085 @Test 086 public void testCheckDatastreamFixity() throws IOException { 087 final String id = getRandomUniqueId(); 088 createObjectAndClose(id); 089 createDatastream(id, "zxc", "foo"); 090 091 final String digestValue = "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"; 092 093 final HttpGet httpGet = getObjMethod(id + "/zxc"); 094 httpGet.setHeader("Want-Digest", "sha"); 095 try (final CloseableHttpResponse response = execute(httpGet)) { 096 assertEquals(OK.getStatusCode(), getStatus(response)); 097 assertDigestEquals(response, "sha", digestValue); 098 } 099 } 100 101 @Test 102 public void testCheckDatastreamFixityMD5() throws IOException { 103 final String id = getRandomUniqueId(); 104 final String digestValue = "888c09337d6869fa48bbeff802ddd05a"; 105 106 final HttpPut put = putObjMethod(id, "text/plain", "text-body"); 107 put.setHeader("Digest", "md5=" + digestValue); 108 assertEquals("Did not create successfully!", CREATED.getStatusCode(), getStatus(put)); 109 110 final HttpGet getFixity = getObjMethod(id); 111 getFixity.setHeader("Want-Digest", "md5"); 112 try (final CloseableHttpResponse response = execute(getFixity)) { 113 assertEquals(OK.getStatusCode(), getStatus(response)); 114 assertDigestEquals(response, "md5", digestValue); 115 } 116 } 117 118 @Test 119 public void testMultipleAlgorithms() throws IOException { 120 final String id = getRandomUniqueId(); 121 final String payload = "This is some wonderful test text payload"; 122 final Map<String, String> shas = Map.of("sha","dce87c75d05bb3893ff5e72f0596754bcf47cfed", 123 "sha-256", "55bf3e1931dffbf16fa7bfb80c000c04e6f17c75471a622af3b11a3511e17843", 124 "md5", "6ddfc43540f3e0abdb5c54ca832bc9e3"); 125 126 final HttpPut httpPut = putObjMethod(id,"text/plain", payload); 127 httpPut.setHeader("Digest", shas.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()) 128 .collect(Collectors.joining(","))); 129 assertEquals(CREATED.getStatusCode(), getStatus(httpPut)); 130 131 final HttpGet httpGet = getObjMethod(id); 132 httpGet.setHeader("Want-Digest", String.join(",", shas.keySet())); 133 try (final CloseableHttpResponse response = execute(httpGet)) { 134 assertEquals(OK.getStatusCode(), getStatus(response)); 135 for (final Map.Entry<String, String> digest : shas.entrySet()) { 136 assertDigestEquals(response, digest.getKey(), digest.getValue()); 137 } 138 } 139 140 } 141 142 @Test 143 public void testBinaryFixity() throws IOException { 144 final String id = getRandomUniqueId(); 145 final FedoraId resourceId = FedoraId.create(id); 146 final String uri = createDatastream(id, "foo"); 147 148 try (final CloseableDataset dataset = 149 getDataset(new HttpGet(uri + "/" + FCR_FIXITY))) { 150 final DatasetGraph graphStore = dataset.asDatasetGraph(); 151 logger.debug("Got binary content fixity triples {}", graphStore); 152 final Iterator<Quad> stmtIt = graphStore.find(ANY, ANY, HAS_FIXITY_RESULT.asNode(), ANY); 153 assertTrue(stmtIt.hasNext()); 154 assertTrue(graphStore.contains(ANY, ANY, HAS_FIXITY_STATE.asNode(), createLiteral("SUCCESS"))); 155 assertTrue(graphStore.contains(ANY, ANY, HAS_MESSAGE_DIGEST.asNode(), ANY)); 156 assertTrue(graphStore.contains(ANY, ANY, HAS_SIZE.asNode(), createLiteral("3", IntegerType))); 157 } 158 159 160 final var ocflDesc = ocflRepo.describeVersion( 161 ObjectVersionId.head(resourceId.getResourceId())); 162 final var storagePath = ocflDesc.getFile(id).getStorageRelativePath(); 163 Files.writeString(ocflConfig.getOcflRepoRoot().resolve(storagePath), 164 "corrupted!", StandardCharsets.UTF_8, StandardOpenOption.APPEND); 165 166 try (final CloseableDataset dataset = 167 getDataset(new HttpGet(uri + "/" + FCR_FIXITY))) { 168 final DatasetGraph graphStore = dataset.asDatasetGraph(); 169 logger.debug("Got binary content fixity triples {}", graphStore); 170 final Iterator<Quad> stmtIt = graphStore.find(ANY, ANY, HAS_FIXITY_RESULT.asNode(), ANY); 171 assertTrue(stmtIt.hasNext()); 172 assertTrue(graphStore.contains(ANY, ANY, HAS_FIXITY_STATE.asNode(), createLiteral("BAD_CHECKSUM"))); 173 assertTrue(graphStore.contains(ANY, ANY, HAS_MESSAGE_DIGEST.asNode(), ANY)); 174 assertTrue(graphStore.contains(ANY, ANY, HAS_SIZE.asNode(), createLiteral("3", IntegerType))); 175 } 176 } 177 178 179 @Test 180 public void testBinaryVersionFixity() throws IOException { 181 final String id = getRandomUniqueId(); 182 createDatastream(id,"foo"); 183 184 logger.debug("Creating binary content version ..."); 185 final String mementoUri = postVersion(id); 186 187 try (final CloseableDataset dataset = 188 getDataset(new HttpGet(mementoUri + "/" + FCR_FIXITY))) { 189 final DatasetGraph graphStore = dataset.asDatasetGraph(); 190 logger.debug("Got binary content versioned fixity triples {}", graphStore); 191 final Iterator<Quad> stmtIt = graphStore.find(ANY, ANY, HAS_FIXITY_RESULT.asNode(), ANY); 192 assertTrue(stmtIt.hasNext()); 193 assertTrue(graphStore.contains(ANY, ANY, HAS_FIXITY_STATE.asNode(), createLiteral("SUCCESS"))); 194 assertTrue(graphStore.contains(ANY, ANY, HAS_MESSAGE_DIGEST.asNode(), ANY)); 195 assertTrue(graphStore.contains(ANY, ANY, HAS_SIZE.asNode(), createLiteral("3", IntegerType))); 196 } 197 } 198 199 @Test 200 public void testInvalidMethods() throws Exception { 201 final String id = getRandomUniqueId(); 202 final String fixityId = id + "/" + FCR_FIXITY; 203 createDatastream(id,"foo"); 204 assertEquals(METHOD_NOT_ALLOWED.getStatusCode(), getStatus(patchObjMethod(fixityId))); 205 assertEquals(METHOD_NOT_ALLOWED.getStatusCode(), getStatus(deleteObjMethod(fixityId))); 206 assertEquals(METHOD_NOT_ALLOWED.getStatusCode(), getStatus(putObjMethod(fixityId))); 207 assertEquals(METHOD_NOT_ALLOWED.getStatusCode(), getStatus(postObjMethod(fixityId))); 208 assertEquals(METHOD_NOT_ALLOWED.getStatusCode(), getStatus(headObjMethod(fixityId))); 209 } 210 211 private static String postVersion(final String path) throws IOException { 212 logger.debug("Posting version"); 213 final HttpPost postVersion = postObjMethod(path + "/fcr:versions"); 214 try (final CloseableHttpResponse response = execute(postVersion)) { 215 assertEquals(CREATED.getStatusCode(), getStatus(response)); 216 final String locationHeader = getLocation(response); 217 assertNotNull("No version location header found", locationHeader); 218 return locationHeader; 219 } 220 } 221 222 private static void assertDigestEquals(final CloseableHttpResponse response, final String digestName, 223 final String digestValue) { 224 final String digest = response.getFirstHeader("Digest").getValue(); 225 final Map<String, String> digestHeaders = decodeDigestHeader(digest); 226 assertTrue(digestHeaders.containsKey(digestName)); 227 assertEquals("Mismatch on " + digestName + " value", digestValue, digestHeaders.get(digestName)); 228 } 229 230}