|
9 | 9 | */ |
10 | 10 | package io.pravega.schemaregistry.server.rest.auth; |
11 | 11 |
|
12 | | -import com.google.common.base.Charsets; |
13 | | -import com.google.common.base.Preconditions; |
14 | | -import com.google.common.base.Strings; |
15 | | -import io.pravega.auth.AuthConstants; |
16 | | -import io.pravega.auth.AuthException; |
17 | 12 | import io.pravega.auth.AuthHandler; |
18 | | -import io.pravega.auth.AuthenticationException; |
19 | 13 | import io.pravega.auth.ServerConfig; |
20 | | -import io.pravega.controller.server.security.auth.StrongPasswordProcessor; |
21 | | -import io.pravega.controller.server.security.auth.UserPrincipal; |
| 14 | +import io.pravega.controller.server.rpc.grpc.impl.GRPCServerConfigImpl; |
| 15 | +import io.pravega.controller.server.security.auth.handler.impl.PasswordAuthHandler; |
22 | 16 | import io.pravega.schemaregistry.server.rest.ServiceConfig; |
23 | | -import lombok.Data; |
| 17 | +import io.pravega.schemaregistry.service.Config; |
24 | 18 | import lombok.extern.slf4j.Slf4j; |
25 | 19 |
|
26 | | -import java.io.BufferedReader; |
27 | | -import java.io.FileReader; |
28 | | -import java.io.IOException; |
29 | | -import java.security.NoSuchAlgorithmException; |
30 | | -import java.security.Principal; |
31 | | -import java.security.spec.InvalidKeySpecException; |
32 | | -import java.util.Arrays; |
33 | | -import java.util.Base64; |
34 | | -import java.util.List; |
35 | | -import java.util.concurrent.CompletionException; |
36 | | -import java.util.concurrent.ConcurrentHashMap; |
37 | | -import java.util.stream.Collectors; |
38 | | - |
39 | 20 | @Slf4j |
40 | | -public class BasicAuthHandler implements AuthHandler { |
41 | | - |
42 | | - private final ConcurrentHashMap<String, PravegaACls> userMap; |
43 | | - private final StrongPasswordProcessor encryptor; |
44 | | - |
45 | | - public BasicAuthHandler() { |
46 | | - userMap = new ConcurrentHashMap<>(); |
47 | | - encryptor = StrongPasswordProcessor.builder().build(); |
48 | | - } |
49 | | - |
50 | | - private void loadPasswordFile(String userPasswordFile) { |
51 | | - log.debug("Loading {}", userPasswordFile); |
52 | | - |
53 | | - try (FileReader reader = new FileReader(userPasswordFile); |
54 | | - BufferedReader lineReader = new BufferedReader(reader)) { |
55 | | - String line; |
56 | | - while (!Strings.isNullOrEmpty(line = lineReader.readLine())) { |
57 | | - if (line.startsWith("#")) { |
58 | | - continue; |
59 | | - } |
60 | | - String[] userFields = line.split(":"); |
61 | | - if (userFields.length >= 2) { |
62 | | - String acls; |
63 | | - if (userFields.length == 2) { |
64 | | - acls = ""; |
65 | | - } else { |
66 | | - acls = userFields[2]; |
67 | | - } |
68 | | - userMap.put(userFields[0], new PravegaACls(userFields[1], getAcls(acls))); |
69 | | - } |
70 | | - } |
71 | | - } catch (IOException e) { |
72 | | - throw new CompletionException(e); |
73 | | - } |
74 | | - } |
75 | | - |
76 | | - @Override |
77 | | - public String getHandlerName() { |
78 | | - return AuthConstants.BASIC; |
79 | | - } |
80 | | - |
81 | | - @Override |
82 | | - public Principal authenticate(String token) throws AuthException { |
83 | | - String[] parts = parseToken(token); |
84 | | - String userName = parts[0]; |
85 | | - char[] password = parts[1].toCharArray(); |
86 | | - |
87 | | - try { |
88 | | - if (userMap.containsKey(userName) && encryptor.checkPassword(password, userMap.get(userName).encryptedPassword)) { |
89 | | - return new UserPrincipal(userName); |
90 | | - } |
91 | | - throw new AuthenticationException("User authentication exception"); |
92 | | - } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { |
93 | | - log.warn("Exception during password authentication", e); |
94 | | - throw new AuthenticationException(e); |
95 | | - } finally { |
96 | | - Arrays.fill(password, '0'); // Zero out the password for security. |
97 | | - } |
98 | | - } |
99 | | - |
100 | | - @Override |
101 | | - public Permissions authorize(String resource, Principal principal) { |
102 | | - String userName = principal.getName(); |
103 | | - |
104 | | - if (Strings.isNullOrEmpty(userName) || !userMap.containsKey(userName)) { |
105 | | - throw new CompletionException(new AuthenticationException(userName)); |
106 | | - } |
107 | | - return authorizeForUser(userMap.get(userName), resource); |
108 | | - } |
109 | | - |
| 21 | +public class BasicAuthHandler extends PasswordAuthHandler implements AuthHandler { |
110 | 22 | @Override |
111 | 23 | public void initialize(ServerConfig serverConfig) { |
112 | | - loadPasswordFile(((ServiceConfig) serverConfig).getUserPasswordFilePath()); |
| 24 | + super.initialize(GRPCServerConfigImpl |
| 25 | + .builder().port(Config.SERVICE_PORT) |
| 26 | + .userPasswordFile(((ServiceConfig) serverConfig).getUserPasswordFilePath()).build()); |
113 | 27 | } |
| 28 | +} |
114 | 29 |
|
115 | | - private static String[] parseToken(String token) { |
116 | | - String[] parts = new String(Base64.getDecoder().decode(token), Charsets.UTF_8).split(":", 2); |
117 | | - Preconditions.checkArgument(parts.length == 2, "Invalid authorization token"); |
118 | | - return parts; |
119 | | - } |
120 | | - |
121 | | - private Permissions authorizeForUser(PravegaACls pravegaACls, String resource) { |
122 | | - Permissions result = Permissions.NONE; |
123 | | - |
124 | | - /* |
125 | | - * `*` Means a wildcard. |
126 | | - * If It is a direct match, return the ACLs. |
127 | | - * If it is a partial match, the target has to end with a `/` |
128 | | - */ |
129 | | - for (PravegaAcl acl : pravegaACls.acls) { |
130 | | - // Separating into different blocks, to make the code more understandable. |
131 | | - // It makes the code look a bit strange, but it is still simpler and easier to decipher than what it be |
132 | | - // if we combine the conditions. |
133 | | - |
134 | | - if (acl.isResource(resource)) { |
135 | | - // Example: resource = "mygroup", acl-resource = "mygroup" |
136 | | - |
137 | | - result = acl.permissions; |
138 | | - break; |
139 | | - } |
140 | | - |
141 | | - if (acl.isResource("/*") && !resource.contains("/")) { |
142 | | - // Example: resource = "mygroup", acl-resource ="/*" |
143 | | - result = acl.permissions; |
144 | | - break; |
145 | | - } |
146 | | - |
147 | | - if (acl.resourceEndsWith("/") && acl.resourceStartsWith(resource)) { |
148 | | - result = acl.permissions; |
149 | | - break; |
150 | | - } |
151 | | - |
152 | | - // Say, resource is mygroup/schemas. ACL specifies permission for mygroup/*. |
153 | | - // Auth should return the the ACL's permissions in that case. |
154 | | - if (resource.contains("/") && !resource.endsWith("/")) { |
155 | | - String[] values = resource.split("/"); |
156 | | - String res = null; |
157 | | - for (String value : values) { |
158 | | - res = res == null ? value : res + "/" + value; |
159 | | - if (acl.isResource(res + "/*")) { |
160 | | - result = acl.permissions; |
161 | | - break; |
162 | | - } |
163 | | - } |
164 | | - } |
165 | | - |
166 | | - if (acl.isResource("*") && acl.hasHigherPermissionsThan(result)) { |
167 | | - result = acl.permissions; |
168 | | - break; |
169 | | - } |
170 | | - } |
171 | | - return result; |
172 | | - } |
173 | | - |
174 | | - private List<PravegaAcl> getAcls(String aclString) { |
175 | | - return Arrays.stream(aclString.split(";")).map(acl -> { |
176 | | - String[] splits = acl.split(","); |
177 | | - if (splits.length == 0) { |
178 | | - return null; |
179 | | - } |
180 | | - String resource = splits[0]; |
181 | | - String aclVal = "READ"; |
182 | | - if (splits.length >= 2) { |
183 | | - aclVal = splits[1]; |
184 | | - |
185 | | - } |
186 | | - return new PravegaAcl(resource, |
187 | | - Permissions.valueOf(aclVal)); |
188 | | - }).collect(Collectors.toList()); |
189 | | - } |
190 | | - |
191 | | - @Data |
192 | | - private static class PravegaACls { |
193 | | - private final String encryptedPassword; |
194 | | - private final List<PravegaAcl> acls; |
195 | | - } |
196 | | - |
197 | | - @Data |
198 | | - private static class PravegaAcl { |
199 | | - private final String resourceRepresentation; |
200 | | - private final Permissions permissions; |
201 | | - |
202 | | - boolean isResource(String resource) { |
203 | | - return resourceRepresentation.equals(resource); |
204 | | - } |
205 | | - |
206 | | - boolean resourceEndsWith(String resource) { |
207 | | - return resourceRepresentation.endsWith(resource); |
208 | | - } |
209 | 30 |
|
210 | | - boolean resourceStartsWith(String resource) { |
211 | | - return resourceRepresentation.startsWith(resource); |
212 | | - } |
213 | 31 |
|
214 | | - boolean hasHigherPermissionsThan(Permissions input) { |
215 | | - return this.permissions.ordinal() > input.ordinal(); |
216 | | - } |
217 | | - } |
218 | | -} |
0 commit comments