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
2 changes: 1 addition & 1 deletion dashboard/src/views/Administrator/Audits/AuditResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ const AuditResults = ({ componentProps, row }: any) => {
button2Handler={undefined}
maxWidth="lg"
>
<AuditsTab auditResultGuid={currentPurgeResultObj} />
<AuditsTab auditResultGuid={currentPurgeResultObj} loading={false} />
</CustomModal>
)}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,8 @@ jest.mock('@utils/Muiutils', () => ({

jest.mock('@views/DetailPage/EntityDetailTabs/AuditsTab', () => ({
__esModule: true,
default: ({ auditResultGuid }: any) => (
<div data-testid="audits-tab">AuditsTab - {auditResultGuid}</div>
default: ({ auditResultGuid, loading }: any) => (
<div data-testid="audits-tab" data-loading={loading}>AuditsTab - {auditResultGuid}</div>
)
}));

Expand Down Expand Up @@ -425,6 +425,7 @@ describe('AuditResults Component', () => {

expect(screen.getByTestId('modal-title')).toHaveTextContent('Purged Entity Details: guid-1');
expect(screen.getByTestId('audits-tab')).toBeInTheDocument();
expect(screen.getByTestId('audits-tab')).toHaveAttribute('data-loading', 'false');
});

it('should open auto purge modal with correct title', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ const AttributeProperties = ({
</Stack>
</AccordionSummary>
<AccordionDetails>
{loading == undefined || loading || isEmpty(entityData) ? (
{loading === true || (!auditDetails && isEmpty(entityData)) ? (
<>
<SkeletonLoader count={3} animation="wave" />
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ describe('AttributeProperties', () => {
expect(screen.getByTestId('skeleton-loader')).toBeInTheDocument();
});

it('should render loading skeleton when loading is undefined', () => {
it('should render properties when loading is undefined and entityData is available', () => {
render(
<TestWrapper>
<AttributeProperties
Expand All @@ -337,7 +337,7 @@ describe('AttributeProperties', () => {
</TestWrapper>
);

expect(screen.getByTestId('skeleton-loader')).toBeInTheDocument();
expect(screen.queryByTestId('skeleton-loader')).not.toBeInTheDocument();
});

it('should render loading skeleton when entityData is empty', () => {
Expand All @@ -364,6 +364,40 @@ describe('AttributeProperties', () => {
expect(screen.getByTestId('skeleton-loader')).toBeInTheDocument();
});

it('should not render skeleton in auditDetails mode when loading is undefined', () => {
mockUseSelector.mockImplementation((selector: any) =>
selector({ entity: { entityData: {} } })
);
render(
<TestWrapper>
<AttributeProperties
entity={defaultMockEntity}
referredEntities={defaultMockReferredEntities}
loading={undefined}
auditDetails={true}
propertiesName="Technical"
/>
</TestWrapper>
);
expect(screen.queryByTestId('skeleton-loader')).not.toBeInTheDocument();
expect(screen.getByText('Technical Properties')).toBeInTheDocument();
});

it('should render skeleton in auditDetails mode when loading is true', () => {
render(
<TestWrapper>
<AttributeProperties
entity={defaultMockEntity}
referredEntities={defaultMockReferredEntities}
loading={true}
auditDetails={true}
propertiesName="Technical"
/>
</TestWrapper>
);
expect(screen.getByTestId('skeleton-loader')).toBeInTheDocument();
});

it('should render "No Record Found" when properties are empty', () => {
const emptyEntity = {
typeName: 'DataSet',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.apache.atlas.RequestContext;
import org.apache.atlas.annotation.GraphTransaction;
import org.apache.atlas.authorize.AtlasAuthorizationUtils;
import org.apache.atlas.authorize.AtlasEntityAccessRequest;
import org.apache.atlas.authorize.AtlasPrivilege;
import org.apache.atlas.authorize.AtlasRelationshipAccessRequest;
import org.apache.atlas.exception.AtlasBaseException;
Expand Down Expand Up @@ -214,6 +215,7 @@ public AtlasRelationship getById(String guid) throws AtlasBaseException {
LOG.debug("==> getById({})", guid);

AtlasEdge edge = graphHelper.getEdgeForGUID(guid);
verifyRelationshipReadAccess(edge);
AtlasRelationship ret = entityRetriever.mapEdgeToAtlasRelationship(edge);

LOG.debug("<== getById({}): {}", guid, ret);
Expand All @@ -227,6 +229,7 @@ public AtlasRelationshipWithExtInfo getExtInfoById(String guid) throws AtlasBase
LOG.debug("==> getExtInfoById({})", guid);

AtlasEdge edge = graphHelper.getEdgeForGUID(guid);
verifyRelationshipReadAccess(edge);
AtlasRelationshipWithExtInfo ret = entityRetriever.mapEdgeToAtlasRelationshipWithExtInfo(edge);

LOG.debug("<== getExtInfoById({}): {}", guid, ret);
Expand Down Expand Up @@ -771,4 +774,14 @@ private void sendNotifications(AtlasRelationship ret, OperationType relationship
private void createAndQueueTask(String taskType, AtlasEdge relationshipEdge, AtlasRelationship relationship) {
deleteDelegate.getHandler().createAndQueueTask(taskType, relationshipEdge, relationship);
}

private void verifyRelationshipReadAccess(AtlasEdge edge) throws AtlasBaseException {
AtlasEntityHeader end1Entity = entityRetriever.toAtlasEntityHeaderWithClassifications(edge.getOutVertex());
AtlasEntityHeader end2Entity = entityRetriever.toAtlasEntityHeaderWithClassifications(edge.getInVertex());

AtlasAuthorizationUtils.verifyAccess(new AtlasEntityAccessRequest(typeRegistry, AtlasPrivilege.ENTITY_READ, end1Entity),
"read relationship: end1 guid=", end1Entity.getGuid());
AtlasAuthorizationUtils.verifyAccess(new AtlasEntityAccessRequest(typeRegistry, AtlasPrivilege.ENTITY_READ, end2Entity),
"read relationship: end2 guid=", end2Entity.getGuid());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -412,17 +412,16 @@ public AtlasSearchResultDownloadStatus getSearchResultDownloadStatus() throws IO
@Timed
@Path("download/{filename}")
@Produces(MediaType.APPLICATION_OCTET_STREAM)
public Response downloadSearchResultFile(@PathParam("filename") String fileName) {
public Response downloadSearchResultFile(@PathParam("filename") String fileName) throws AtlasBaseException {
File dir = new File(SearchResultDownloadTask.DOWNLOAD_DIR_PATH, RequestContext.getCurrentUser());
File csvFile = new File(dir, fileName);
File csvFile = SearchDownloadFileValidator.resolveDownloadFile(fileName, dir);

if (!csvFile.exists()) {
if (!csvFile.exists() || !csvFile.isFile()) {
return Response.noContent().build();
}

Response.ResponseBuilder response = Response.ok(csvFile);

response.header("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
response.header("Content-Disposition", "attachment; filename=\"" + csvFile.getName() + "\"");

return response.build();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.atlas.web.rest;

import org.apache.atlas.AtlasErrorCode;
import org.apache.atlas.exception.AtlasBaseException;
import org.apache.commons.lang3.StringUtils;

import java.io.File;
import java.io.IOException;

import static org.apache.atlas.repository.store.graph.v2.tasks.searchdownload.SearchResultDownloadTask.CSV_FILE_EXTENSION;

final class SearchDownloadFileValidator {
static final String INVALID_DOWNLOAD_FILE_NAME_MSG = "Invalid download file name";

private SearchDownloadFileValidator() {
}

static File resolveDownloadFile(String fileName, File userDownloadDir) throws AtlasBaseException {
validateFileName(fileName);

File csvFile = new File(userDownloadDir, fileName);

try {
String userDirPath = userDownloadDir.getCanonicalPath();
String filePath = csvFile.getCanonicalPath();

if (!filePath.startsWith(userDirPath + File.separator)) {
throw new AtlasBaseException(AtlasErrorCode.BAD_REQUEST, INVALID_DOWNLOAD_FILE_NAME_MSG);
}
} catch (IOException e) {
throw new AtlasBaseException(AtlasErrorCode.BAD_REQUEST, INVALID_DOWNLOAD_FILE_NAME_MSG);
}

return csvFile;
}

private static void validateFileName(String fileName) throws AtlasBaseException {
if (StringUtils.isBlank(fileName)) {
throw new AtlasBaseException(AtlasErrorCode.BAD_REQUEST, INVALID_DOWNLOAD_FILE_NAME_MSG);
}

if (fileName.indexOf('/') >= 0 || fileName.indexOf('\\') >= 0 || fileName.contains("..")) {
throw new AtlasBaseException(AtlasErrorCode.BAD_REQUEST, INVALID_DOWNLOAD_FILE_NAME_MSG);
}

if (!fileName.endsWith(CSV_FILE_EXTENSION)) {
throw new AtlasBaseException(AtlasErrorCode.BAD_REQUEST, INVALID_DOWNLOAD_FILE_NAME_MSG);
}

if (fileName.indexOf("_BASIC_") <= 0 && fileName.indexOf("_DSL_") <= 0) {
throw new AtlasBaseException(AtlasErrorCode.BAD_REQUEST, INVALID_DOWNLOAD_FILE_NAME_MSG);
}
}
}
Loading