Skip to content

Commit 872293c

Browse files
zhljenajoymajumdar
authored andcommitted
Adding userName based access control for table and database operations. (Netflix#285)
* Adding acl for table and database operations * reverting other minor changes * Refactoring the defaultAuthorizationService
1 parent a3f3f66 commit 872293c

File tree

20 files changed

+599
-37
lines changed

20 files changed

+599
-37
lines changed

metacat-client/src/main/java/com/netflix/metacat/client/module/MetacatErrorDecoder.java

+3
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import com.netflix.metacat.common.exception.MetacatNotSupportedException;
2626
import com.netflix.metacat.common.exception.MetacatPreconditionFailedException;
2727
import com.netflix.metacat.common.exception.MetacatTooManyRequestsException;
28+
import com.netflix.metacat.common.exception.MetacatUnAuthorizedException;
2829
import com.netflix.metacat.common.json.MetacatJson;
2930
import com.netflix.metacat.common.json.MetacatJsonException;
3031
import feign.Response;
@@ -64,6 +65,8 @@ public Exception decode(final String methodKey, final Response response) {
6465
return new MetacatNotSupportedException(message);
6566
case 400: //BAD_REQUEST
6667
return new MetacatBadRequestException(message);
68+
case 403: //Forbidden
69+
return new MetacatUnAuthorizedException(message);
6770
case 404: //NOT_FOUND
6871
return new MetacatNotFoundException(message);
6972
case 409: //CONFLICT
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/*
2+
* Copyright 2018 Netflix, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
18+
package com.netflix.metacat.common.server.properties;
19+
20+
import com.netflix.metacat.common.QualifiedName;
21+
import com.netflix.metacat.common.exception.MetacatException;
22+
import com.netflix.servo.util.VisibleForTesting;
23+
import lombok.Data;
24+
import lombok.NonNull;
25+
import org.apache.commons.lang3.StringUtils;
26+
27+
import java.util.Arrays;
28+
import java.util.HashMap;
29+
import java.util.HashSet;
30+
import java.util.Map;
31+
import java.util.Set;
32+
33+
/**
34+
* Properties related to authorization.
35+
*
36+
* @author zhenl
37+
* @since 1.2.0
38+
*/
39+
@Data
40+
public class AuthorizationServiceProperties {
41+
private boolean enabled;
42+
@NonNull
43+
private CreateAcl createAcl = new CreateAcl();
44+
@NonNull
45+
private DeleteAcl deleteAcl = new DeleteAcl();
46+
47+
/**
48+
* Create Acl properties.
49+
*
50+
* @author zhenl
51+
* @since 1.2.0
52+
*/
53+
@Data
54+
public static class CreateAcl {
55+
private String createAclStr;
56+
private Map<QualifiedName, Set<String>> createAclMap;
57+
58+
/**
59+
* Get the create acl map.
60+
*
61+
* @return The create acl map
62+
*/
63+
public Map<QualifiedName, Set<String>> getCreateAclMap() {
64+
if (createAclMap == null) {
65+
createAclMap = createAclStr == null ? new HashMap<>()
66+
: getAclMap(createAclStr);
67+
}
68+
return createAclMap;
69+
}
70+
}
71+
72+
/**
73+
* Delete Acl properties.
74+
*
75+
* @author zhenl
76+
* @since 1.2.0
77+
*/
78+
@Data
79+
public static class DeleteAcl {
80+
private String deleteAclStr;
81+
private Map<QualifiedName, Set<String>> deleteAclMap;
82+
83+
/**
84+
* Get the delete acl map.
85+
*
86+
* @return The delete acl map
87+
*/
88+
public Map<QualifiedName, Set<String>> getDeleteAclMap() {
89+
if (deleteAclMap == null) {
90+
deleteAclMap = deleteAclStr == null ? new HashMap<>()
91+
: getAclMap(deleteAclStr);
92+
}
93+
return deleteAclMap;
94+
}
95+
}
96+
97+
/**
98+
* Parse the configuration to get operation control. The control is at userName level
99+
* and the controlled operations include create, delete, and rename for table.
100+
* The format is below.
101+
* db1:user1,user2|db2:user1,user2
102+
*
103+
* @param aclConfig the config strings for dbs
104+
* @return acl config
105+
*/
106+
@VisibleForTesting
107+
private static Map<QualifiedName, Set<String>> getAclMap(final String aclConfig) {
108+
final Map<QualifiedName, Set<String>> aclMap = new HashMap<>();
109+
try {
110+
for (String aclstr : StringUtils.split(aclConfig, "|")) {
111+
for (QualifiedName key
112+
: PropertyUtils.delimitedStringsToQualifiedNamesList(
113+
aclstr.substring(0, aclstr.indexOf(":")), ',')) {
114+
aclMap.put(key,
115+
new HashSet<>(Arrays.asList(
116+
StringUtils.split(aclstr.substring(aclstr.indexOf(":") + 1), ","))));
117+
}
118+
119+
}
120+
121+
} catch (Exception e) {
122+
throw new MetacatException("metacat acl property parsing error" + e.getMessage());
123+
}
124+
return aclMap;
125+
}
126+
}

metacat-common-server/src/main/java/com/netflix/metacat/common/server/properties/Config.java

+27
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import com.netflix.metacat.common.QualifiedName;
1717

1818
import java.util.List;
19+
import java.util.Map;
1920
import java.util.Set;
2021

2122
/**
@@ -38,14 +39,18 @@ public interface Config {
3839

3940
/**
4041
* Enable publishing partitions to elastic search.
42+
*
4143
* @return true if publishing partitions to elastic search is enabled
4244
*/
4345
boolean isElasticSearchPublishPartitionEnabled();
46+
4447
/**
4548
* Enable publishing metacat es error logs to elastic search.
49+
*
4650
* @return true if publishing metacat es error logs to elastic search is enabled
4751
*/
4852
boolean isElasticSearchPublishMetacatLogEnabled();
53+
4954
/**
5055
* Elastic search cluster name.
5156
*
@@ -348,4 +353,26 @@ public interface Config {
348353
* @return true if cache is enabled
349354
*/
350355
boolean isCacheEnabled();
356+
357+
/**
358+
* Enable authorization.
359+
*
360+
* @return true if authorization is enabled
361+
*/
362+
boolean isAuthorizationEnabled();
363+
364+
/**
365+
* Get the metacat create acl property.
366+
*
367+
* @return The metacat create acl property
368+
*/
369+
Map<QualifiedName, Set<String>> getMetacatCreateAcl();
370+
371+
/**
372+
* Get the metacat delete acl property.
373+
*
374+
* @return The metacat delete acl property
375+
*/
376+
Map<QualifiedName, Set<String>> getMetacatDeleteAcl();
351377
}
378+

metacat-common-server/src/main/java/com/netflix/metacat/common/server/properties/DefaultConfigImpl.java

+17
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import javax.annotation.Nonnull;
2424
import java.util.List;
25+
import java.util.Map;
2526
import java.util.Set;
2627

2728
/**
@@ -419,4 +420,20 @@ public Set<QualifiedName> getNamesEnabledForDefinitionMetadataDelete() {
419420
public boolean isCacheEnabled() {
420421
return this.metacatProperties.getCache().isEnabled();
421422
}
423+
424+
425+
@Override
426+
public boolean isAuthorizationEnabled() {
427+
return this.metacatProperties.getAuthorization().isEnabled();
428+
}
429+
430+
@Override
431+
public Map<QualifiedName, Set<String>> getMetacatCreateAcl() {
432+
return this.metacatProperties.getAuthorization().getCreateAcl().getCreateAclMap();
433+
}
434+
435+
@Override
436+
public Map<QualifiedName, Set<String>> getMetacatDeleteAcl() {
437+
return this.metacatProperties.getAuthorization().getDeleteAcl().getDeleteAclMap();
438+
}
422439
}

metacat-common-server/src/main/java/com/netflix/metacat/common/server/properties/MetacatProperties.java

+2
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,6 @@ public class MetacatProperties {
6161
private UserMetadata usermetadata = new UserMetadata();
6262
@NonNull
6363
private CacheProperties cache = new CacheProperties();
64+
@NonNull
65+
private AuthorizationServiceProperties authorization = new AuthorizationServiceProperties();
6466
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2018 Netflix, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
18+
package com.netflix.metacat.common.server.usermetadata;
19+
20+
import com.netflix.metacat.common.QualifiedName;
21+
22+
/**
23+
* Authorization Service API.
24+
*
25+
* @author zhenl
26+
* @since 1.2.0
27+
*/
28+
public interface AuthorizationService {
29+
/**
30+
* Check metacat acl property if this operation is permitted.
31+
*
32+
* @param userName username
33+
* @param name qualified Name
34+
* @param op operation
35+
*/
36+
default void checkPermission(final String userName,
37+
final QualifiedName name,
38+
final MetacatOperation op) {
39+
40+
}
41+
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright 2018 Netflix, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
18+
package com.netflix.metacat.common.server.usermetadata;
19+
20+
import com.netflix.metacat.common.QualifiedName;
21+
import com.netflix.metacat.common.exception.MetacatUnAuthorizedException;
22+
import com.netflix.metacat.common.server.properties.Config;
23+
24+
import java.util.Map;
25+
import java.util.Set;
26+
27+
/**
28+
* Config based authorization service implementation.
29+
*
30+
* @author zhenl
31+
* @since 1.2.0
32+
*/
33+
public class DefaultAuthorizationService implements AuthorizationService {
34+
private final Config config;
35+
36+
/**
37+
* Constructor.
38+
*
39+
* @param config metacat config
40+
*/
41+
public DefaultAuthorizationService(
42+
final Config config
43+
) {
44+
this.config = config;
45+
}
46+
47+
/**
48+
* {@inheritDoc}
49+
*/
50+
@Override
51+
public void checkPermission(final String userName,
52+
final QualifiedName name,
53+
final MetacatOperation op) {
54+
if (config.isAuthorizationEnabled()) {
55+
switch (op) {
56+
case CREATE:
57+
checkPermit(config.getMetacatCreateAcl(), userName, name, op);
58+
break;
59+
case RENAME:
60+
case DELETE:
61+
checkPermit(config.getMetacatDeleteAcl(), userName, name, op);
62+
break;
63+
default:
64+
65+
}
66+
}
67+
}
68+
69+
/**
70+
* Check at database level.
71+
*/
72+
private void checkPermit(final Map<QualifiedName, Set<String>> accessACL,
73+
final String userName,
74+
final QualifiedName name,
75+
final MetacatOperation op) {
76+
final Set<String> users =
77+
accessACL.get(QualifiedName.ofDatabase(name.getCatalogName(), name.getDatabaseName()));
78+
if ((users != null) && !users.isEmpty() && !users.contains(userName)) {
79+
throw new MetacatUnAuthorizedException(String.format("%s is not permitted for %s %s",
80+
userName, op.name(), name
81+
));
82+
}
83+
}
84+
85+
}

0 commit comments

Comments
 (0)