Skip to content

Commit 7615468

Browse files
chenkinsdkocher
andauthored
Map properties in custom namespace to ACLs and File ID
Co-authored-by: David Kocher <[email protected]>
1 parent 4a3115b commit 7615468

21 files changed

+1245
-111
lines changed
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
package ch.cyberduck.core.ctera;
2+
3+
import ch.cyberduck.core.Acl;
4+
import ch.cyberduck.core.LocaleFactory;
5+
import ch.cyberduck.core.Path;
6+
import ch.cyberduck.core.PathAttributes;
7+
import ch.cyberduck.core.dav.DAVAttributesFinderFeature;
8+
import ch.cyberduck.core.dav.DAVPathEncoder;
9+
import ch.cyberduck.core.dav.DAVSession;
10+
import ch.cyberduck.core.exception.AccessDeniedException;
11+
import ch.cyberduck.core.exception.BackgroundException;
12+
13+
import org.apache.logging.log4j.LogManager;
14+
import org.apache.logging.log4j.Logger;
15+
16+
import javax.xml.namespace.QName;
17+
import java.io.IOException;
18+
import java.text.MessageFormat;
19+
import java.util.Arrays;
20+
import java.util.Collections;
21+
import java.util.HashSet;
22+
import java.util.List;
23+
import java.util.Map;
24+
import java.util.Set;
25+
import java.util.stream.Collectors;
26+
import java.util.stream.Stream;
27+
28+
import com.github.sardine.DavResource;
29+
30+
public class CteraAttributesFinderFeature extends DAVAttributesFinderFeature {
31+
private static final Logger log = LogManager.getLogger(CteraAttributesFinderFeature.class);
32+
33+
public static final String CTERA_GUID = "guid";
34+
public static final String CTERA_NAMESPACE_URI = "http://www.ctera.com/ns";
35+
public static final String CTERA_NAMESPACE_PREFIX = "ctera";
36+
/**
37+
* Read Data: Allows or denies viewing data in files.
38+
*/
39+
public static final Acl.Role READPERMISSION = new Acl.Role("readpermission");
40+
/**
41+
* Write Data: Allows or denies making changes to a file and overwriting existing content.
42+
*/
43+
public static final Acl.Role WRITEPERMISSION = new Acl.Role("writepermission");
44+
/**
45+
* Execute File: Allows or denies running program (executable) files.
46+
* Files only.
47+
*/
48+
public static final Acl.Role EXECUTEPERMISSION = new Acl.Role("executepermission");
49+
/**
50+
* Allows or denies deleting the file or folder. If you don't have Delete permission on a file or folder,
51+
* you can still delete it if you have been granted Delete Subfolders and Files on the parent folder.
52+
*/
53+
public static final Acl.Role DELETEPERMISSION = new Acl.Role("deletepermission");
54+
/**
55+
* Create Files: Allows or denies creating files within the folder.
56+
* Directories only.
57+
* For future use, not used yet.
58+
*/
59+
@Deprecated
60+
public static final Acl.Role CREATEFILEPERMISSION = new Acl.Role(WRITEPERMISSION.getName());
61+
/**
62+
* Create Folders: Allows or denies creating subfolders within the folder.
63+
* Directories only.
64+
*/
65+
public static final Acl.Role CREATEDIRECTORIESPERMISSION = new Acl.Role("createdirectoriespermission");
66+
67+
public static final Set<Acl.Role> ALL_ACL_ROLES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
68+
READPERMISSION, WRITEPERMISSION, EXECUTEPERMISSION, DELETEPERMISSION, CREATEDIRECTORIESPERMISSION
69+
)));
70+
public static final Set<QName> ALL_ACL_QN = Collections.unmodifiableSet(ALL_ACL_ROLES.stream().map(CteraAttributesFinderFeature::toQn).collect(Collectors.toSet()));
71+
public static final QName GUID_QN = new QName(CteraAttributesFinderFeature.CTERA_NAMESPACE_URI, CteraAttributesFinderFeature.CTERA_GUID, CteraAttributesFinderFeature.CTERA_NAMESPACE_PREFIX);
72+
73+
private final DAVSession session;
74+
75+
public CteraAttributesFinderFeature(final DAVSession session) {
76+
super(session);
77+
this.session = session;
78+
}
79+
80+
public static QName toQn(final Acl.Role role) {
81+
return new QName(CTERA_NAMESPACE_URI, role.getName(), CTERA_NAMESPACE_PREFIX);
82+
}
83+
84+
public static String toProp(final QName qn) {
85+
return qn.getLocalPart();
86+
}
87+
88+
public static Acl.Role toRole(final QName qn) {
89+
return new Acl.Role(toProp(qn));
90+
}
91+
92+
/**
93+
* @param customProps Custom properties from DAV resource
94+
* @return Known ACLs in custom namespace
95+
*/
96+
public static Acl toAcl(final Map<String, String> customProps) {
97+
if(customProps.keySet().stream().anyMatch(property -> ALL_ACL_QN.stream().anyMatch(qn -> toProp(qn).equals(property)))) {
98+
return new Acl(new Acl.CanonicalUser(), customProps.entrySet().stream().filter(property -> ALL_ACL_QN.stream().anyMatch(qn -> toProp(qn).equals(property.getKey())))
99+
.filter(property -> Boolean.parseBoolean(property.getValue())).map(property -> new Acl.Role(property.getKey())).distinct().toArray(Acl.Role[]::new));
100+
}
101+
// Ignore ACL in preflight checks as none of the custom roles is found
102+
return Acl.EMPTY;
103+
}
104+
105+
/**
106+
* @param file File with ACLs to validate role
107+
* @param role Assumed role
108+
* @throws AccessDeniedException ACLs do not contain role
109+
*/
110+
protected static void assumeRole(final Path file, final Acl.Role role) throws BackgroundException {
111+
assumeRole(file, file.getName(), role);
112+
}
113+
114+
protected static void assumeRole(final Path file, final String filename, final Acl.Role role) throws BackgroundException {
115+
final Acl acl = file.attributes().getAcl();
116+
if(acl == Acl.EMPTY) {
117+
if(log.isWarnEnabled()) {
118+
log.warn(String.format("Missing ACL for %s", file));
119+
}
120+
return;
121+
}
122+
if(!acl.get(new Acl.CanonicalUser()).contains(role)) {
123+
if(log.isWarnEnabled()) {
124+
log.warn(String.format("ACL %s for %s does not include %s", acl, file, role));
125+
}
126+
if(role == READPERMISSION) {
127+
throw new AccessDeniedException(MessageFormat.format(LocaleFactory.localizedString("Download {0} failed", "Error"),
128+
filename)).withFile(file);
129+
}
130+
if(role == WRITEPERMISSION) {
131+
throw new AccessDeniedException(MessageFormat.format(LocaleFactory.localizedString("Upload {0} failed", "Error"),
132+
filename)).withFile(file);
133+
}
134+
if(role == DELETEPERMISSION) {
135+
throw new AccessDeniedException(MessageFormat.format(
136+
LocaleFactory.localizedString("Cannot delete {0}", "Error"), file.getName())).withFile(file);
137+
}
138+
if(role == CREATEDIRECTORIESPERMISSION) {
139+
throw new AccessDeniedException(MessageFormat.format(LocaleFactory.localizedString("Cannot create folder {0}", "Error"),
140+
filename)).withFile(file);
141+
}
142+
throw new AccessDeniedException(MessageFormat.format(
143+
LocaleFactory.localizedString("Cannot create {0}", "Error"), file.getName())).withFile(file);
144+
}
145+
}
146+
147+
@Override
148+
protected List<DavResource> list(final Path file) throws IOException {
149+
return session.getClient().list(new DAVPathEncoder().encode(file), 0, Collections.unmodifiableSet(Stream.concat(
150+
Stream.of(GUID_QN), ALL_ACL_QN.stream()
151+
).collect(Collectors.toSet())));
152+
}
153+
154+
@Override
155+
public PathAttributes toAttributes(final DavResource resource) {
156+
final Map<String, String> customProps = resource.getCustomProps();
157+
final Acl acl = toAcl(customProps);
158+
final PathAttributes attributes = super.toAttributes(resource);
159+
if(customProps.containsKey(CTERA_GUID)) {
160+
attributes.setFileId(customProps.get(CTERA_GUID));
161+
}
162+
return attributes.withAcl(acl);
163+
}
164+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package ch.cyberduck.core.ctera;
2+
3+
/*
4+
* Copyright (c) 2002-2024 iterate GmbH. All rights reserved.
5+
* https://cyberduck.io/
6+
*
7+
* This program is free software; you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*/
17+
18+
import ch.cyberduck.core.Path;
19+
import ch.cyberduck.core.dav.DAVCopyFeature;
20+
import ch.cyberduck.core.exception.BackgroundException;
21+
22+
import static ch.cyberduck.core.ctera.CteraAttributesFinderFeature.*;
23+
24+
public class CteraCopyFeature extends DAVCopyFeature {
25+
26+
private final CteraAttributesFinderFeature attributes;
27+
28+
public CteraCopyFeature(final CteraSession session, final CteraAttributesFinderFeature attributes) {
29+
super(session);
30+
this.attributes = attributes;
31+
}
32+
33+
public CteraCopyFeature(final CteraSession session) {
34+
super(session);
35+
this.attributes = new CteraAttributesFinderFeature(session);
36+
}
37+
38+
@Override
39+
public void preflight(final Path source, final Path target) throws BackgroundException {
40+
// defaults to Acl.EMPTY (disabling role checking) if target does not exist
41+
assumeRole(target, WRITEPERMISSION);
42+
// no createfilespermission required for now
43+
if(source.isDirectory()) {
44+
assumeRole(target.getParent(), target.getName(), CREATEDIRECTORIESPERMISSION);
45+
}
46+
}
47+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package ch.cyberduck.core.ctera;
2+
3+
/*
4+
* Copyright (c) 2002-2024 iterate GmbH. All rights reserved.
5+
* https://cyberduck.io/
6+
*
7+
* This program is free software; you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*/
17+
18+
import ch.cyberduck.core.Path;
19+
import ch.cyberduck.core.dav.DAVDeleteFeature;
20+
import ch.cyberduck.core.exception.BackgroundException;
21+
22+
import static ch.cyberduck.core.ctera.CteraAttributesFinderFeature.DELETEPERMISSION;
23+
import static ch.cyberduck.core.ctera.CteraAttributesFinderFeature.assumeRole;
24+
25+
public class CteraDeleteFeature extends DAVDeleteFeature {
26+
27+
public CteraDeleteFeature(final CteraSession session) {
28+
super(session);
29+
}
30+
31+
@Override
32+
public void preflight(Path file) throws BackgroundException {
33+
assumeRole(file, DELETEPERMISSION);
34+
}
35+
}

ctera/src/main/java/ch/cyberduck/core/ctera/CteraDirectoryFeature.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,21 @@
2323

2424
import java.text.MessageFormat;
2525

26+
import static ch.cyberduck.core.ctera.CteraAttributesFinderFeature.CREATEDIRECTORIESPERMISSION;
27+
import static ch.cyberduck.core.ctera.CteraAttributesFinderFeature.assumeRole;
28+
import static ch.cyberduck.core.ctera.CteraTouchFeature.validate;
29+
2630
public class CteraDirectoryFeature extends DAVDirectoryFeature {
2731

2832
public CteraDirectoryFeature(final CteraSession session) {
29-
super(session);
33+
super(session, new CteraAttributesFinderFeature(session));
3034
}
3135

3236
@Override
3337
public void preflight(final Path workdir, final String filename) throws BackgroundException {
34-
if(!CteraTouchFeature.validate(filename)) {
38+
if(!validate(filename)) {
3539
throw new InvalidFilenameException(MessageFormat.format(LocaleFactory.localizedString("Cannot create folder {0}", "Error"), filename));
3640
}
41+
assumeRole(workdir, filename, CREATEDIRECTORIESPERMISSION);
3742
}
3843
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package ch.cyberduck.core.ctera;
2+
3+
/*
4+
* Copyright (c) 2002-2024 iterate GmbH. All rights reserved.
5+
* https://cyberduck.io/
6+
*
7+
* This program is free software; you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*/
17+
18+
import ch.cyberduck.core.Path;
19+
import ch.cyberduck.core.dav.DAVListService;
20+
import ch.cyberduck.core.dav.DAVPathEncoder;
21+
22+
import java.io.IOException;
23+
import java.util.Collections;
24+
import java.util.List;
25+
import java.util.stream.Collectors;
26+
import java.util.stream.Stream;
27+
28+
import com.github.sardine.DavResource;
29+
30+
import static ch.cyberduck.core.ctera.CteraAttributesFinderFeature.ALL_ACL_QN;
31+
import static ch.cyberduck.core.ctera.CteraAttributesFinderFeature.GUID_QN;
32+
33+
public class CteraListService extends DAVListService {
34+
35+
private final CteraSession session;
36+
37+
public CteraListService(final CteraSession session) {
38+
super(session, new CteraAttributesFinderFeature(session));
39+
this.session = session;
40+
}
41+
42+
@Override
43+
protected List<DavResource> list(final Path directory) throws IOException {
44+
return session.getClient().list(new DAVPathEncoder().encode(directory), 1, Collections.unmodifiableSet(Stream.concat(
45+
Stream.of(GUID_QN), ALL_ACL_QN.stream()
46+
).collect(Collectors.toSet())));
47+
}
48+
}

ctera/src/main/java/ch/cyberduck/core/ctera/CteraMoveFeature.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,33 @@
2323

2424
import java.text.MessageFormat;
2525

26+
import static ch.cyberduck.core.ctera.CteraAttributesFinderFeature.*;
27+
2628
public class CteraMoveFeature extends DAVMoveFeature {
2729

30+
private final CteraAttributesFinderFeature attributes;
31+
2832
public CteraMoveFeature(final CteraSession session) {
2933
super(session);
34+
this.attributes = new CteraAttributesFinderFeature(session);
35+
}
36+
37+
public CteraMoveFeature(final CteraSession session, final CteraAttributesFinderFeature attributes) {
38+
super(session);
39+
this.attributes = attributes;
3040
}
3141

3242
@Override
3343
public void preflight(final Path source, final Path target) throws BackgroundException {
3444
if(!CteraTouchFeature.validate(target.getName())) {
3545
throw new InvalidFilenameException(MessageFormat.format(LocaleFactory.localizedString("Cannot rename {0}", "Error"), source.getName())).withFile(source);
3646
}
47+
assumeRole(source, DELETEPERMISSION);
48+
// defaults to Acl.EMPTY (disabling role checking) if target does not exist
49+
assumeRole(target, WRITEPERMISSION);
50+
// no createfilespermission required for now
51+
if(source.isDirectory()) {
52+
assumeRole(target.getParent(), target.getName(), CREATEDIRECTORIESPERMISSION);
53+
}
3754
}
3855
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package ch.cyberduck.core.ctera;
2+
3+
/*
4+
* Copyright (c) 2002-2024 iterate GmbH. All rights reserved.
5+
* https://cyberduck.io/
6+
*
7+
* This program is free software; you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*/
17+
18+
import ch.cyberduck.core.Path;
19+
import ch.cyberduck.core.dav.DAVReadFeature;
20+
import ch.cyberduck.core.exception.BackgroundException;
21+
22+
import static ch.cyberduck.core.ctera.CteraAttributesFinderFeature.READPERMISSION;
23+
import static ch.cyberduck.core.ctera.CteraAttributesFinderFeature.assumeRole;
24+
25+
public class CteraReadFeature extends DAVReadFeature {
26+
27+
public CteraReadFeature(final CteraSession session) {
28+
super(session);
29+
}
30+
31+
@Override
32+
public void preflight(Path file) throws BackgroundException {
33+
assumeRole(file, READPERMISSION);
34+
}
35+
}

0 commit comments

Comments
 (0)