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

Map properties in custom namespace to ACLs and File ID #15468

Merged
merged 79 commits into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
d75f832
Map custom XML fields to Acl and Permission in Ctera session (CTERA-1…
chenkins Dec 14, 2023
8d9c84a
Use junit4 for parameterized tests.
chenkins Dec 20, 2023
2efe403
Move CteraCustomACL constants to CteraAclPermissionFeature.
chenkins Dec 20, 2023
2045835
Make all static fields final.
chenkins Jan 3, 2024
59e45bd
Use available API Permission.Action to build Permission from Acl.
chenkins Jan 3, 2024
a52f1f1
Extract anonymous Ctera features.
chenkins Jan 3, 2024
1c76ced
Extract checkCteraRole to CteraAclPermissionFeature.
chenkins Jan 3, 2024
461f7cf
Cleanup unused variable (CTERA-136).
chenkins Jan 8, 2024
85a751f
Add ctera:guid to custom props/path attributes (CTERA-137).
chenkins Jan 8, 2024
dc567c1
Use fileId instead of versionId for ctera:guid (CTERA-137).
chenkins Jan 11, 2024
2bfc55f
Cleanup parameter naming, duplicated parameters, formatting (CTERA-137).
chenkins Jan 11, 2024
8d5c4e0
Review exception message (CTERA-137).
chenkins Jan 11, 2024
6f11fe9
Update comments (CTERA-137).
chenkins Jan 11, 2024
a458502
Make Ctera features public (CTERA-136).
chenkins Jan 19, 2024
eff9ee5
Remove embedded webdav tweaking (CTERA-137).
chenkins Jan 19, 2024
efa406f
Post rebase fix (CTERA-136).
chenkins Mar 18, 2024
cd10ee2
Add Javadoc (CTERA-136).
chenkins Mar 18, 2024
788f050
Use Ctera features in Ctera integration tests (CTERA-136).
chenkins Mar 18, 2024
17fada9
Add Javadoc (CTERA-136).
chenkins Mar 18, 2024
5f77b36
Custom namespace and prefix for ctera permissions (CTERA-136).
chenkins Mar 18, 2024
4106da9
Custom namespace and prefix for ctera guid (CTERA-137).
chenkins Mar 18, 2024
167f7b2
Extract constant for ctera:guid (CTERA-137).
chenkins Mar 19, 2024
e0fed28
Duplicate session in CteraListService instead of making it protected …
chenkins Mar 19, 2024
40d92f3
Move CTERA_GUID constant to CteraAttributesFinderFeature (CTERA-137).
chenkins Mar 20, 2024
58a0600
Inline variables.
chenkins Mar 20, 2024
eba5363
Use setFileId when return value is not needed.
chenkins Mar 20, 2024
1288c6e
Localize exception.
chenkins Mar 20, 2024
2ea145d
Remove unused code.
chenkins Mar 20, 2024
c723ebf
Add test to show guid is same in attributes returned from touch and m…
chenkins Mar 20, 2024
c9b01e6
Add test showing guid is retained when moving file (CTERA-137).
chenkins Mar 20, 2024
099bca2
Log warning if the roles required for an operation are missing in add…
chenkins Mar 21, 2024
652f703
Add preflight test for CTERA move feature.
chenkins Mar 21, 2024
5941043
Use assertThrows.
chenkins Mar 21, 2024
fbec38a
Implement backwards compatibility by using Acl.EMPTY to skip new pref…
chenkins Mar 21, 2024
ac52577
Clean-up TODOs.
chenkins Mar 21, 2024
440fe7a
Implement preflight 3 tests for all CTERA features.
chenkins Mar 21, 2024
d8df02b
Remove commented out code.
chenkins Mar 21, 2024
878b26e
Return Acl.EMPTY when none of the ACL properties are found.
chenkins Mar 22, 2024
535c1d9
Remove role traversepermission as not supported.
chenkins Mar 22, 2024
f400d7c
Revert to no metadata feature in ctera.
chenkins Mar 22, 2024
09b8665
Drop CteraAclPermissionFeature.
chenkins Mar 22, 2024
7bdfc8a
Add override annotation.
chenkins Mar 22, 2024
14f00fb
New line.
dkocher Mar 23, 2024
90221f9
Inline.
dkocher Mar 23, 2024
6408da6
Remove super preflight.
dkocher Mar 23, 2024
ec9018c
Rename and Javadoc.
dkocher Mar 23, 2024
2b5d8c7
New line.
dkocher Mar 23, 2024
0e6226e
Formatting.
dkocher Mar 23, 2024
bc7b2d0
Rename and Javadoc.
dkocher Mar 23, 2024
b5dc666
Rename constants.
dkocher Mar 23, 2024
ced083a
Static imports.
dkocher Mar 23, 2024
943ad57
Define as set.
dkocher Mar 23, 2024
6820785
Remove unsupported roles.
dkocher Mar 23, 2024
df3d0e2
Delete failing ETag assumptions.
dkocher Mar 23, 2024
9cda7de
Fix test.
dkocher Mar 23, 2024
5958836
Delete test no longer applicable.
dkocher Mar 23, 2024
555c9d4
Fix test.
dkocher Mar 23, 2024
4f1ed7c
Disable test for LOCK not supported.
dkocher Mar 23, 2024
3992141
Delete redundant checks.
dkocher Mar 24, 2024
4442c00
Logging.
dkocher Mar 24, 2024
d56e01f
Add proper localization for errors.
dkocher Mar 24, 2024
b618e20
Add tests.
dkocher Mar 24, 2024
1bbe711
Replace with incomprehensible one-liner.
dkocher Mar 24, 2024
56293c2
Delete redundant null check.
dkocher Mar 24, 2024
4d740ff
Revert.
dkocher Mar 24, 2024
57a323d
Extract field.
dkocher Mar 24, 2024
9d09768
Add tests.
dkocher Mar 24, 2024
aae1426
Review test.
dkocher Mar 24, 2024
4c4b161
Rename permission for creating folders.
dkocher Mar 26, 2024
1a31f55
Verify acls from manually set up folders in CTERA portal.
chenkins Mar 26, 2024
d1c6507
Review tests.
dkocher Mar 27, 2024
b7c8b4e
Verify acls from manually set up folders in CTERA portal continued.
chenkins Mar 27, 2024
6b2e138
Remove TODO to be ignored.
chenkins Mar 27, 2024
7313594
Fix role name.
dkocher Mar 28, 2024
7f08a86
Fix tests.
dkocher Mar 28, 2024
63055b2
Add missing parameter.
dkocher Mar 28, 2024
2bbf7d0
Add README.md in ctera module.
chenkins Apr 2, 2024
535edc4
Remove testing on createfilespermission. Add target exists test on cp…
chenkins Apr 2, 2024
03b8b3d
Avoid long running find operation in preflight checks.
chenkins Apr 3, 2024
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package ch.cyberduck.core.ctera;

import ch.cyberduck.core.Acl;
import ch.cyberduck.core.LocaleFactory;
import ch.cyberduck.core.Path;
import ch.cyberduck.core.PathAttributes;
import ch.cyberduck.core.dav.DAVAttributesFinderFeature;
import ch.cyberduck.core.dav.DAVPathEncoder;
import ch.cyberduck.core.dav.DAVSession;
import ch.cyberduck.core.exception.AccessDeniedException;
import ch.cyberduck.core.exception.BackgroundException;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import javax.xml.namespace.QName;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.github.sardine.DavResource;

public class CteraAttributesFinderFeature extends DAVAttributesFinderFeature {
chenkins marked this conversation as resolved.
Show resolved Hide resolved
private static final Logger log = LogManager.getLogger(CteraAttributesFinderFeature.class);

public static final String CTERA_GUID = "guid";
public static final String CTERA_NAMESPACE_URI = "http://www.ctera.com/ns";
public static final String CTERA_NAMESPACE_PREFIX = "ctera";
/**
* Read Data: Allows or denies viewing data in files.
*/
public static final Acl.Role READPERMISSION = new Acl.Role("readpermission");
/**
* Write Data: Allows or denies making changes to a file and overwriting existing content.
*/
public static final Acl.Role WRITEPERMISSION = new Acl.Role("writepermission");
/**
* Execute File: Allows or denies running program (executable) files.
* Files only.
*/
public static final Acl.Role EXECUTEPERMISSION = new Acl.Role("executepermission");
/**
* Allows or denies deleting the file or folder. If you don't have Delete permission on a file or folder,
* you can still delete it if you have been granted Delete Subfolders and Files on the parent folder.
*/
public static final Acl.Role DELETEPERMISSION = new Acl.Role("deletepermission");
/**
* Create Files: Allows or denies creating files within the folder.
* Directories only.
* For future use, not used yet.
*/
@Deprecated
public static final Acl.Role CREATEFILEPERMISSION = new Acl.Role(WRITEPERMISSION.getName());
dkocher marked this conversation as resolved.
Show resolved Hide resolved
/**
* Create Folders: Allows or denies creating subfolders within the folder.
* Directories only.
*/
public static final Acl.Role CREATEDIRECTORIESPERMISSION = new Acl.Role("createdirectoriespermission");

public static final Set<Acl.Role> ALL_ACL_ROLES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
READPERMISSION, WRITEPERMISSION, EXECUTEPERMISSION, DELETEPERMISSION, CREATEDIRECTORIESPERMISSION
)));
public static final Set<QName> ALL_ACL_QN = Collections.unmodifiableSet(ALL_ACL_ROLES.stream().map(CteraAttributesFinderFeature::toQn).collect(Collectors.toSet()));
public static final QName GUID_QN = new QName(CteraAttributesFinderFeature.CTERA_NAMESPACE_URI, CteraAttributesFinderFeature.CTERA_GUID, CteraAttributesFinderFeature.CTERA_NAMESPACE_PREFIX);

private final DAVSession session;

public CteraAttributesFinderFeature(final DAVSession session) {
super(session);
this.session = session;
}

public static QName toQn(final Acl.Role role) {
return new QName(CTERA_NAMESPACE_URI, role.getName(), CTERA_NAMESPACE_PREFIX);
}

public static String toProp(final QName qn) {
return qn.getLocalPart();
}

public static Acl.Role toRole(final QName qn) {
return new Acl.Role(toProp(qn));
}

/**
* @param customProps Custom properties from DAV resource
* @return Known ACLs in custom namespace
*/
public static Acl toAcl(final Map<String, String> customProps) {
if(customProps.keySet().stream().anyMatch(property -> ALL_ACL_QN.stream().anyMatch(qn -> toProp(qn).equals(property)))) {
return new Acl(new Acl.CanonicalUser(), customProps.entrySet().stream().filter(property -> ALL_ACL_QN.stream().anyMatch(qn -> toProp(qn).equals(property.getKey())))
.filter(property -> Boolean.parseBoolean(property.getValue())).map(property -> new Acl.Role(property.getKey())).distinct().toArray(Acl.Role[]::new));
}
// Ignore ACL in preflight checks as none of the custom roles is found
return Acl.EMPTY;
}

/**
* @param file File with ACLs to validate role
* @param role Assumed role
* @throws AccessDeniedException ACLs do not contain role
*/
protected static void assumeRole(final Path file, final Acl.Role role) throws BackgroundException {
assumeRole(file, file.getName(), role);
}

protected static void assumeRole(final Path file, final String filename, final Acl.Role role) throws BackgroundException {
final Acl acl = file.attributes().getAcl();
if(acl == Acl.EMPTY) {
if(log.isWarnEnabled()) {
log.warn(String.format("Missing ACL for %s", file));
}
return;
}
if(!acl.get(new Acl.CanonicalUser()).contains(role)) {
if(log.isWarnEnabled()) {
log.warn(String.format("ACL %s for %s does not include %s", acl, file, role));
}
if(role == READPERMISSION) {
throw new AccessDeniedException(MessageFormat.format(LocaleFactory.localizedString("Download {0} failed", "Error"),
filename)).withFile(file);
}
if(role == WRITEPERMISSION) {
throw new AccessDeniedException(MessageFormat.format(LocaleFactory.localizedString("Upload {0} failed", "Error"),
filename)).withFile(file);
}
if(role == DELETEPERMISSION) {
throw new AccessDeniedException(MessageFormat.format(
LocaleFactory.localizedString("Cannot delete {0}", "Error"), file.getName())).withFile(file);
}
if(role == CREATEDIRECTORIESPERMISSION) {
throw new AccessDeniedException(MessageFormat.format(LocaleFactory.localizedString("Cannot create folder {0}", "Error"),
filename)).withFile(file);
}
throw new AccessDeniedException(MessageFormat.format(
LocaleFactory.localizedString("Cannot create {0}", "Error"), file.getName())).withFile(file);
}
}

@Override
protected List<DavResource> list(final Path file) throws IOException {
return session.getClient().list(new DAVPathEncoder().encode(file), 0, Collections.unmodifiableSet(Stream.concat(
Stream.of(GUID_QN), ALL_ACL_QN.stream()
).collect(Collectors.toSet())));
}

@Override
public PathAttributes toAttributes(final DavResource resource) {
final Map<String, String> customProps = resource.getCustomProps();
final Acl acl = toAcl(customProps);
final PathAttributes attributes = super.toAttributes(resource);
if(customProps.containsKey(CTERA_GUID)) {
attributes.setFileId(customProps.get(CTERA_GUID));
}
return attributes.withAcl(acl);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package ch.cyberduck.core.ctera;

/*
* Copyright (c) 2002-2024 iterate GmbH. All rights reserved.
* https://cyberduck.io/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/

import ch.cyberduck.core.Path;
import ch.cyberduck.core.dav.DAVCopyFeature;
import ch.cyberduck.core.exception.BackgroundException;

import static ch.cyberduck.core.ctera.CteraAttributesFinderFeature.*;

public class CteraCopyFeature extends DAVCopyFeature {

private final CteraAttributesFinderFeature attributes;

public CteraCopyFeature(final CteraSession session, final CteraAttributesFinderFeature attributes) {
super(session);
this.attributes = attributes;
}

public CteraCopyFeature(final CteraSession session) {
super(session);
this.attributes = new CteraAttributesFinderFeature(session);
}

@Override
public void preflight(final Path source, final Path target) throws BackgroundException {
// defaults to Acl.EMPTY (disabling role checking) if target does not exist
assumeRole(target, WRITEPERMISSION);
// no createfilespermission required for now
if(source.isDirectory()) {
assumeRole(target.getParent(), target.getName(), CREATEDIRECTORIESPERMISSION);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package ch.cyberduck.core.ctera;

/*
* Copyright (c) 2002-2024 iterate GmbH. All rights reserved.
* https://cyberduck.io/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/

import ch.cyberduck.core.Path;
import ch.cyberduck.core.dav.DAVDeleteFeature;
import ch.cyberduck.core.exception.BackgroundException;

import static ch.cyberduck.core.ctera.CteraAttributesFinderFeature.DELETEPERMISSION;
import static ch.cyberduck.core.ctera.CteraAttributesFinderFeature.assumeRole;

public class CteraDeleteFeature extends DAVDeleteFeature {

public CteraDeleteFeature(final CteraSession session) {
super(session);
}

@Override
public void preflight(Path file) throws BackgroundException {
chenkins marked this conversation as resolved.
Show resolved Hide resolved
assumeRole(file, DELETEPERMISSION);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,21 @@

import java.text.MessageFormat;

import static ch.cyberduck.core.ctera.CteraAttributesFinderFeature.CREATEDIRECTORIESPERMISSION;
import static ch.cyberduck.core.ctera.CteraAttributesFinderFeature.assumeRole;
import static ch.cyberduck.core.ctera.CteraTouchFeature.validate;

public class CteraDirectoryFeature extends DAVDirectoryFeature {

public CteraDirectoryFeature(final CteraSession session) {
super(session);
super(session, new CteraAttributesFinderFeature(session));
}

@Override
public void preflight(final Path workdir, final String filename) throws BackgroundException {
if(!CteraTouchFeature.validate(filename)) {
if(!validate(filename)) {
throw new InvalidFilenameException(MessageFormat.format(LocaleFactory.localizedString("Cannot create folder {0}", "Error"), filename));
}
assumeRole(workdir, filename, CREATEDIRECTORIESPERMISSION);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package ch.cyberduck.core.ctera;

/*
* Copyright (c) 2002-2024 iterate GmbH. All rights reserved.
* https://cyberduck.io/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/

import ch.cyberduck.core.Path;
import ch.cyberduck.core.dav.DAVListService;
import ch.cyberduck.core.dav.DAVPathEncoder;

import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.github.sardine.DavResource;

import static ch.cyberduck.core.ctera.CteraAttributesFinderFeature.ALL_ACL_QN;
import static ch.cyberduck.core.ctera.CteraAttributesFinderFeature.GUID_QN;

public class CteraListService extends DAVListService {

private final CteraSession session;

public CteraListService(final CteraSession session) {
super(session, new CteraAttributesFinderFeature(session));
this.session = session;
}

@Override
protected List<DavResource> list(final Path directory) throws IOException {
return session.getClient().list(new DAVPathEncoder().encode(directory), 1, Collections.unmodifiableSet(Stream.concat(
Stream.of(GUID_QN), ALL_ACL_QN.stream()
).collect(Collectors.toSet())));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,33 @@

import java.text.MessageFormat;

import static ch.cyberduck.core.ctera.CteraAttributesFinderFeature.*;

public class CteraMoveFeature extends DAVMoveFeature {
chenkins marked this conversation as resolved.
Show resolved Hide resolved

private final CteraAttributesFinderFeature attributes;

public CteraMoveFeature(final CteraSession session) {
super(session);
this.attributes = new CteraAttributesFinderFeature(session);
}

public CteraMoveFeature(final CteraSession session, final CteraAttributesFinderFeature attributes) {
super(session);
this.attributes = attributes;
}

@Override
public void preflight(final Path source, final Path target) throws BackgroundException {
if(!CteraTouchFeature.validate(target.getName())) {
throw new InvalidFilenameException(MessageFormat.format(LocaleFactory.localizedString("Cannot rename {0}", "Error"), source.getName())).withFile(source);
}
assumeRole(source, DELETEPERMISSION);
// defaults to Acl.EMPTY (disabling role checking) if target does not exist
assumeRole(target, WRITEPERMISSION);
// no createfilespermission required for now
if(source.isDirectory()) {
assumeRole(target.getParent(), target.getName(), CREATEDIRECTORIESPERMISSION);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package ch.cyberduck.core.ctera;

/*
* Copyright (c) 2002-2024 iterate GmbH. All rights reserved.
* https://cyberduck.io/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/

import ch.cyberduck.core.Path;
import ch.cyberduck.core.dav.DAVReadFeature;
import ch.cyberduck.core.exception.BackgroundException;

import static ch.cyberduck.core.ctera.CteraAttributesFinderFeature.READPERMISSION;
import static ch.cyberduck.core.ctera.CteraAttributesFinderFeature.assumeRole;

public class CteraReadFeature extends DAVReadFeature {

public CteraReadFeature(final CteraSession session) {
super(session);
}

@Override
public void preflight(Path file) throws BackgroundException {
assumeRole(file, READPERMISSION);
}
}
Loading
Loading