Skip to content

Commit 0d50e71

Browse files
committed
Add owner and reader to credentials
1 parent af82806 commit 0d50e71

40 files changed

+968
-434
lines changed

jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@
7272
import org.apache.zeppelin.jdbc.security.JDBCSecurityImpl;
7373
import org.apache.zeppelin.scheduler.Scheduler;
7474
import org.apache.zeppelin.scheduler.SchedulerFactory;
75-
import org.apache.zeppelin.user.UserCredentials;
7675
import org.apache.zeppelin.user.UsernamePassword;
76+
import org.apache.zeppelin.user.UsernamePasswords;
7777

7878
/**
7979
* JDBC interpreter for Zeppelin. This interpreter can also be used for accessing HAWQ,
@@ -248,6 +248,7 @@ public void open() {
248248
this.sqlSplitter = new SqlSplitter();
249249
}
250250

251+
@Override
251252
protected boolean isKerboseEnabled() {
252253
if (!isEmpty(getProperty("zeppelin.jdbc.auth.type"))) {
253254
UserGroupInformation.AuthenticationMethod authType = JDBCSecurityImpl.getAuthType(properties);
@@ -390,9 +391,9 @@ private boolean existAccountInBaseProperty(String propertyKey) {
390391

391392
private UsernamePassword getUsernamePassword(InterpreterContext interpreterContext,
392393
String entity) {
393-
UserCredentials uc = interpreterContext.getAuthenticationInfo().getUserCredentials();
394-
if (uc != null) {
395-
return uc.getUsernamePassword(entity);
394+
UsernamePasswords ups = interpreterContext.getAuthenticationInfo().getUsernamePasswords();
395+
if (ups != null) {
396+
return ups.getUsernamePassword(entity);
396397
}
397398
return null;
398399
}
@@ -711,6 +712,7 @@ private boolean isDDLCommand(int updatedCount, int columnCount) throws SQLExcept
711712
return updatedCount < 0 && columnCount <= 0 ? true : false;
712713
}
713714

715+
@Override
714716
public InterpreterResult executePrecode(InterpreterContext interpreterContext)
715717
throws InterpreterException {
716718
InterpreterResult interpreterResult = null;

jdbc/src/test/java/org/apache/zeppelin/jdbc/JDBCInterpreterTest.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@
2828
import org.apache.zeppelin.scheduler.ParallelScheduler;
2929
import org.apache.zeppelin.scheduler.Scheduler;
3030
import org.apache.zeppelin.user.AuthenticationInfo;
31-
import org.apache.zeppelin.user.UserCredentials;
3231
import org.apache.zeppelin.user.UsernamePassword;
32+
import org.apache.zeppelin.user.UsernamePasswords;
3333
import org.junit.jupiter.api.BeforeEach;
3434
import org.junit.jupiter.api.Test;
3535

@@ -92,6 +92,7 @@ public static Properties getJDBCTestProperties() {
9292
return p;
9393
}
9494

95+
@Override
9596
@BeforeEach
9697
public void setUp() throws Exception {
9798
Class.forName("org.h2.Driver");
@@ -531,13 +532,13 @@ private Properties getDBProperty(String dbPrefix,
531532

532533
private AuthenticationInfo getUserAuth(String user, String entityName, String dbUser,
533534
String dbPassword) {
534-
UserCredentials userCredentials = new UserCredentials();
535+
UsernamePasswords userCredentials = new UsernamePasswords();
535536
if (entityName != null && dbUser != null && dbPassword != null) {
536537
UsernamePassword up = new UsernamePassword(dbUser, dbPassword);
537538
userCredentials.putUsernamePassword(entityName, up);
538539
}
539540
AuthenticationInfo authInfo = new AuthenticationInfo();
540-
authInfo.setUserCredentials(userCredentials);
541+
authInfo.setUsernamePasswords(userCredentials);
541542
authInfo.setUser(user);
542543
return authInfo;
543544
}

zeppelin-interpreter/src/main/java/org/apache/zeppelin/user/AuthenticationInfo.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ public class AuthenticationInfo implements JsonSerializable {
4141
String user;
4242
Set<String> roles;
4343
String ticket;
44-
UserCredentials userCredentials;
44+
// enitiy -> UsernamePassword
45+
UsernamePasswords usernamePasswords;
4546
public static final AuthenticationInfo ANONYMOUS = new AuthenticationInfo("anonymous", new HashSet<>(),
4647
"anonymous");
4748

@@ -89,8 +90,8 @@ public void setRoles(Set<String> roles) {
8990
this.roles = roles;
9091
}
9192

92-
public List<String> getUsersAndRoles() {
93-
List<String> usersAndRoles = new ArrayList<>();
93+
public Set<String> getUsersAndRoles() {
94+
Set<String> usersAndRoles = new HashSet<>();
9495
if (roles != null) {
9596
usersAndRoles.addAll(roles);
9697
}
@@ -109,12 +110,12 @@ public void setTicket(String ticket) {
109110
this.ticket = ticket;
110111
}
111112

112-
public UserCredentials getUserCredentials() {
113-
return userCredentials;
113+
public UsernamePasswords getUsernamePasswords() {
114+
return usernamePasswords;
114115
}
115116

116-
public void setUserCredentials(UserCredentials userCredentials) {
117-
this.userCredentials = userCredentials;
117+
public void setUsernamePasswords(UsernamePasswords usernamePasswords) {
118+
this.usernamePasswords = usernamePasswords;
118119
}
119120

120121
public static boolean isAnonymous(AuthenticationInfo subject) {

zeppelin-interpreter/src/main/java/org/apache/zeppelin/user/UsernamePassword.java

Lines changed: 18 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,26 @@
11
/*
2-
* Licensed to the Apache Software Foundation (ASF) under one or more
3-
* contributor license agreements. See the NOTICE file distributed with
4-
* this work for additional information regarding copyright ownership.
5-
* The ASF licenses this file to You under the Apache License, Version 2.0
6-
* (the "License"); you may not use this file except in compliance with
7-
* the License. You may obtain a copy of the License at
8-
*
9-
* http://www.apache.org/licenses/LICENSE-2.0
10-
*
11-
* Unless required by applicable law or agreed to in writing, software
12-
* distributed under the License is distributed on an "AS IS" BASIS,
13-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14-
* See the License for the specific language governing permissions and
15-
* limitations under the License.
16-
*/
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
1717

1818
package org.apache.zeppelin.user;
1919

20-
/**
21-
* Username and Password POJO
22-
*/
2320
public class UsernamePassword {
24-
private String username;
25-
private String password;
21+
22+
private final String username;
23+
private final String password;
2624

2725
public UsernamePassword(String username, String password) {
2826
this.username = username;
@@ -33,18 +31,10 @@ public String getUsername() {
3331
return username;
3432
}
3533

36-
public void setUsername(String username) {
37-
this.username = username;
38-
}
39-
4034
public String getPassword() {
4135
return password;
4236
}
4337

44-
public void setPassword(String password) {
45-
this.password = password;
46-
}
47-
4838
@Override
4939
public String toString() {
5040
return "UsernamePassword{" +

zeppelin-interpreter/src/main/java/org/apache/zeppelin/user/UserCredentials.java renamed to zeppelin-interpreter/src/main/java/org/apache/zeppelin/user/UsernamePasswords.java

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,35 +17,45 @@
1717

1818
package org.apache.zeppelin.user;
1919

20-
import java.util.Map;
21-
import java.util.concurrent.ConcurrentHashMap;
20+
import java.util.HashMap;
2221

2322
/**
2423
* User Credentials POJO
24+
*
25+
* Key: Credential entity
26+
*
27+
* Value: credentials
2528
*/
26-
public class UserCredentials {
27-
private Map<String, UsernamePassword> userCredentials = new ConcurrentHashMap<>();
29+
public class UsernamePasswords extends HashMap<String, UsernamePassword> {
30+
31+
/**
32+
*
33+
*/
34+
private static final long serialVersionUID = 1L;
2835

2936
public UsernamePassword getUsernamePassword(String entity) {
30-
return userCredentials.get(entity);
37+
return get(entity);
3138
}
3239

33-
public void putUsernamePassword(String entity, UsernamePassword up) {
34-
userCredentials.put(entity, up);
40+
/**
41+
* Wrapper method for {@link HashMap#remove(Object)}
42+
*/
43+
public UsernamePassword removeUsernamePassword(String entity) {
44+
return remove(entity);
3545
}
3646

37-
public void removeUsernamePassword(String entity) {
38-
userCredentials.remove(entity);
47+
/**
48+
* Wrapper method for {@link HashMap#put(Object, Object)}
49+
*/
50+
public UsernamePassword putUsernamePassword(String entity, UsernamePassword up) {
51+
return put(entity, up);
3952
}
4053

54+
/**
55+
* Wrapper method for {@link HashMap#containsKey(Object)}
56+
*/
4157
public boolean existUsernamePassword(String entity) {
42-
return userCredentials.containsKey(entity);
58+
return containsKey(entity);
4359
}
4460

45-
@Override
46-
public String toString() {
47-
return "UserCredentials{" +
48-
"userCredentials=" + userCredentials +
49-
'}';
50-
}
5161
}

zeppelin-server/src/main/java/org/apache/zeppelin/rest/AbstractRestApi.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,14 @@ protected AbstractRestApi(AuthenticationService authenticationService) {
4242
protected ServiceContext getServiceContext() {
4343
AuthenticationInfo authInfo = new AuthenticationInfo(authenticationService.getPrincipal());
4444
authInfo.setRoles(authenticationService.getAssociatedRoles());
45+
return new ServiceContext(authInfo, getUserAndRoles());
46+
}
47+
48+
protected Set<String> getUserAndRoles() {
4549
Set<String> userAndRoles = new HashSet<>();
4650
userAndRoles.add(authenticationService.getPrincipal());
4751
userAndRoles.addAll(authenticationService.getAssociatedRoles());
48-
return new ServiceContext(authInfo, userAndRoles);
52+
return userAndRoles;
4953
}
5054

5155
public static class RestServiceCallback<T> extends SimpleServiceCallback<T> {

zeppelin-server/src/main/java/org/apache/zeppelin/rest/CredentialRestApi.java

Lines changed: 35 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
package org.apache.zeppelin.rest;
1919

2020
import java.io.IOException;
21+
import java.util.HashSet;
22+
import java.util.Set;
23+
2124
import javax.inject.Inject;
2225
import javax.inject.Singleton;
2326
import javax.ws.rs.DELETE;
@@ -30,12 +33,13 @@
3033
import javax.ws.rs.core.Response.Status;
3134

3235
import org.apache.commons.lang3.StringUtils;
36+
import org.apache.zeppelin.notebook.AuthorizationService;
3337
import org.apache.zeppelin.rest.message.CredentialRequest;
3438
import org.apache.zeppelin.server.JsonResponse;
3539
import org.apache.zeppelin.service.AuthenticationService;
40+
import org.apache.zeppelin.user.Credential;
3641
import org.apache.zeppelin.user.Credentials;
37-
import org.apache.zeppelin.user.UserCredentials;
38-
import org.apache.zeppelin.user.UsernamePassword;
42+
import org.apache.zeppelin.user.CredentialsMgr;
3943
import org.slf4j.Logger;
4044
import org.slf4j.LoggerFactory;
4145

@@ -45,18 +49,18 @@
4549
@Singleton
4650
public class CredentialRestApi extends AbstractRestApi {
4751
private static final Logger LOGGER = LoggerFactory.getLogger(CredentialRestApi.class);
48-
private final Credentials credentials;
52+
private final CredentialsMgr credentialsMgr;
4953

5054
@Inject
51-
public CredentialRestApi(Credentials credentials, AuthenticationService authenticationService) {
55+
public CredentialRestApi(CredentialsMgr credentials, AuthenticationService authenticationService) {
5256
super(authenticationService);
53-
this.credentials = credentials;
57+
this.credentialsMgr = credentials;
5458
}
5559

5660
/**
5761
* Put User Credentials REST API.
5862
*
59-
* @param message - JSON with entity, username, password.
63+
* @param message - JSON with entity, username, password and shares
6064
* @return JSON with status.OK
6165
*/
6266
@PUT
@@ -67,12 +71,20 @@ public Response putCredentials(String message) {
6771
}
6872

6973
String user = authenticationService.getPrincipal();
70-
LOGGER.info("Update credentials for user {} entity {}", user, request.getEntity());
71-
UserCredentials uc;
74+
Set<String> roles = authenticationService.getAssociatedRoles();
75+
LOGGER.info("Update credential entity {} by user {} with roles {}", request.getEntity(), user, roles);
7276
try {
73-
uc = credentials.getUserCredentials(user);
74-
uc.putUsernamePassword(request.getEntity(), new UsernamePassword(request.getUsername(), request.getPassword()));
75-
credentials.putUserCredentials(user, uc);
77+
Credential credOld = credentialsMgr.getCredentialByEntity(request.getEntity());
78+
if (credOld != null && !credentialsMgr.isOwner(request.getEntity(), getUserAndRoles())) {
79+
return new JsonResponse<>(Status.FORBIDDEN).build();
80+
}
81+
// Ensure that the owner does not lose access to a created credential.
82+
Set<String> owner = new HashSet<String>(request.getOwners());
83+
if (!AuthorizationService.isMember(getUserAndRoles(), owner)) {
84+
owner.add(user);
85+
}
86+
Credential credNew = new Credential(request.getUsername(), request.getPassword(), request.getReader(), owner);
87+
credentialsMgr.putCredentialsEntity(request.getEntity(), credNew);
7688
return new JsonResponse<>(Status.OK).build();
7789
} catch (IOException e) {
7890
LOGGER.error(e.getMessage(), e);
@@ -88,37 +100,10 @@ public Response putCredentials(String message) {
88100
@GET
89101
public Response getCredentials() {
90102
String user = authenticationService.getPrincipal();
91-
LOGGER.info("getCredentials for user {} ", user);
92-
UserCredentials uc;
93-
try {
94-
uc = credentials.getUserCredentials(user);
95-
return new JsonResponse<>(Status.OK, uc).build();
96-
} catch (IOException e) {
97-
LOGGER.error(e.getMessage(), e);
98-
return new JsonResponse<>(Status.INTERNAL_SERVER_ERROR).build();
99-
}
100-
}
101-
102-
/**
103-
* Remove User Credentials REST API.
104-
*
105-
* @return JSON with status.OK
106-
*/
107-
@DELETE
108-
public Response removeCredentials() {
109-
String user = authenticationService.getPrincipal();
110-
LOGGER.info("removeCredentials for user {} ", user);
111-
UserCredentials uc;
112-
try {
113-
uc = credentials.removeUserCredentials(user);
114-
if (uc == null) {
115-
return new JsonResponse<>(Status.NOT_FOUND).build();
116-
}
117-
return new JsonResponse<>(Status.OK).build();
118-
} catch (IOException e) {
119-
LOGGER.error(e.getMessage(), e);
120-
return new JsonResponse<>(Status.INTERNAL_SERVER_ERROR).build();
121-
}
103+
Set<String> roles = authenticationService.getAssociatedRoles();
104+
LOGGER.info("getCredentials for user {} with roles {}", user, roles);
105+
Credentials creds = credentialsMgr.getAllReadableCredentials(getUserAndRoles(), true);
106+
return new JsonResponse<>(Status.OK, creds).build();
122107
}
123108

124109
/**
@@ -133,7 +118,14 @@ public Response removeCredentialEntity(@PathParam("entity") String entity) {
133118
String user = authenticationService.getPrincipal();
134119
LOGGER.info("removeCredentialEntity for user {} entity {}", user, entity);
135120
try {
136-
if (!credentials.removeCredentialEntity(user, entity)) {
121+
if (!credentialsMgr.exists(entity)) {
122+
return new JsonResponse<>(Status.NOT_FOUND).build();
123+
}
124+
if (!credentialsMgr.isOwner(entity, getUserAndRoles())) {
125+
return new JsonResponse<>(Status.FORBIDDEN).build();
126+
}
127+
boolean found = credentialsMgr.removeCredentialEntity(entity);
128+
if (!found) {
137129
return new JsonResponse<>(Status.NOT_FOUND).build();
138130
}
139131
return new JsonResponse<>(Status.OK).build();

0 commit comments

Comments
 (0)