1 | package eu.clarin.cmdi.virtualcollectionregistry.gui.auth; |
---|
2 | |
---|
3 | import java.io.BufferedReader; |
---|
4 | import java.io.FileInputStream; |
---|
5 | import java.io.IOException; |
---|
6 | import java.io.InputStream; |
---|
7 | import java.io.InputStreamReader; |
---|
8 | import java.io.UnsupportedEncodingException; |
---|
9 | import java.util.HashMap; |
---|
10 | import java.util.Map; |
---|
11 | import java.util.concurrent.locks.Lock; |
---|
12 | import java.util.concurrent.locks.ReadWriteLock; |
---|
13 | import java.util.concurrent.locks.ReentrantReadWriteLock; |
---|
14 | import java.util.regex.Matcher; |
---|
15 | import java.util.regex.Pattern; |
---|
16 | |
---|
17 | import javax.servlet.FilterConfig; |
---|
18 | import javax.servlet.ServletException; |
---|
19 | import javax.servlet.http.HttpServletRequest; |
---|
20 | import javax.servlet.http.HttpServletResponse; |
---|
21 | |
---|
22 | import org.apache.commons.codec.binary.Base64; |
---|
23 | import org.apache.commons.codec.digest.DigestUtils; |
---|
24 | |
---|
25 | /** |
---|
26 | * |
---|
27 | * @deprecated to be replaced with basic authentication mechanism at container |
---|
28 | * level (e.g. tomcat-users) |
---|
29 | */ |
---|
30 | @Deprecated |
---|
31 | final class BasicAuthStrategy implements AuthStrategy { |
---|
32 | private enum HashMethod { |
---|
33 | PLAIN, MD5, SHA; |
---|
34 | |
---|
35 | public static HashMethod fromString(String s) { |
---|
36 | if ("PLAIN".equalsIgnoreCase(s)) { |
---|
37 | return HashMethod.PLAIN; |
---|
38 | } else if ("MD5".equalsIgnoreCase(s)) { |
---|
39 | return HashMethod.MD5; |
---|
40 | } else if ("SHA".equalsIgnoreCase(s)) { |
---|
41 | return HashMethod.SHA; |
---|
42 | } else { |
---|
43 | return null; |
---|
44 | } |
---|
45 | } |
---|
46 | } // enum Hash |
---|
47 | |
---|
48 | private static final class Entry { |
---|
49 | private final HashMethod type; |
---|
50 | private final String username; |
---|
51 | private final String password; |
---|
52 | private final Map<String, String> attributes; |
---|
53 | |
---|
54 | public Entry(HashMethod type, String username, String password, Map<String, String> attributes) { |
---|
55 | this.type = type; |
---|
56 | this.username = username; |
---|
57 | this.password = password; |
---|
58 | this.attributes = attributes; |
---|
59 | } |
---|
60 | |
---|
61 | public boolean checkPassword(String pw) { |
---|
62 | switch (type) { |
---|
63 | case PLAIN: |
---|
64 | return this.password.equals(pw); |
---|
65 | case MD5: |
---|
66 | final String hash1 = |
---|
67 | DigestUtils.md5Hex(prepare(pw)).toLowerCase(); |
---|
68 | return this.password.equals(hash1); |
---|
69 | case SHA: |
---|
70 | final String hash2 = |
---|
71 | DigestUtils.shaHex(prepare(pw)).toLowerCase(); |
---|
72 | return this.password.equals(hash2); |
---|
73 | } // switch |
---|
74 | return false; |
---|
75 | } |
---|
76 | |
---|
77 | public Map<String, String> getAttributes() { |
---|
78 | return attributes; |
---|
79 | } |
---|
80 | |
---|
81 | private String prepare(String pw) { |
---|
82 | // FIXME: salt should include something random |
---|
83 | return new StringBuilder(this.username) |
---|
84 | .append(':') |
---|
85 | .append(pw) |
---|
86 | .toString(); |
---|
87 | } |
---|
88 | } // class Entry |
---|
89 | |
---|
90 | private static final String CONFIG_PARAM_USERDB_FILE = |
---|
91 | "authfilter.basic.userdb"; |
---|
92 | private final ReadWriteLock userDbLock = |
---|
93 | new ReentrantReadWriteLock(true); |
---|
94 | private final Map<String, Entry> userDb = |
---|
95 | new HashMap<String, Entry>(); |
---|
96 | |
---|
97 | @Override |
---|
98 | public void init(FilterConfig filterConfig, Map<String, String> config) |
---|
99 | throws ServletException { |
---|
100 | String userdb = config.get(CONFIG_PARAM_USERDB_FILE); |
---|
101 | if ((userdb == null) || userdb.isEmpty()) { |
---|
102 | throw new ServletException("missing init parameter '" + |
---|
103 | CONFIG_PARAM_USERDB_FILE + "'"); |
---|
104 | } |
---|
105 | try { |
---|
106 | InputStream in = new FileInputStream(userdb); |
---|
107 | loadUserDatabase(in); |
---|
108 | } catch (IOException e) { |
---|
109 | throw new ServletException("error initializing user database", e); |
---|
110 | } |
---|
111 | } |
---|
112 | |
---|
113 | @Override |
---|
114 | public String getAuthType() { |
---|
115 | return HttpServletRequest.BASIC_AUTH; |
---|
116 | } |
---|
117 | |
---|
118 | @Override |
---|
119 | public void requestAuth(HttpServletRequest request, |
---|
120 | HttpServletResponse response) throws IOException, ServletException { |
---|
121 | response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); |
---|
122 | final String realm = |
---|
123 | "Basic realm=\"CLARIN Virtual Collection Registry\""; |
---|
124 | response.addHeader("WWW-Authenticate", realm); |
---|
125 | } |
---|
126 | |
---|
127 | @Override |
---|
128 | public Result handleAuth(HttpServletRequest request, |
---|
129 | HttpServletResponse response) throws IOException, ServletException { |
---|
130 | String authorization = request.getHeader("Authorization"); |
---|
131 | if (authorization != null) { |
---|
132 | authorization = authorization.trim(); |
---|
133 | if (authorization.isEmpty()) { |
---|
134 | authorization = null; |
---|
135 | } |
---|
136 | } |
---|
137 | Result result = new Result(); |
---|
138 | if (authorization != null) { |
---|
139 | int pos = authorization.indexOf(' '); |
---|
140 | if (pos != -1) { |
---|
141 | final String scheme = authorization.substring(0, pos).trim(); |
---|
142 | if (scheme.equalsIgnoreCase("Basic")) { |
---|
143 | final String credentials = |
---|
144 | decodeBase64(authorization.substring(pos + 1).trim()); |
---|
145 | pos = credentials.indexOf(':'); |
---|
146 | if ((pos != -1) && (pos < credentials.length() - 2)) { |
---|
147 | final String username = credentials.substring(0, pos); |
---|
148 | final String password = credentials.substring(pos + 1); |
---|
149 | if (checkCredential(username, password)) { |
---|
150 | Map<String, String> attr = getAttributes(username); |
---|
151 | // login successful |
---|
152 | result.setPrinicpal( |
---|
153 | new AuthPrincipal(username, attr)); |
---|
154 | result.setAction(Action.CONTINUE_AUTHENTICATED); |
---|
155 | } else { |
---|
156 | // login failed, retry |
---|
157 | result.setAction(Action.RETRY); |
---|
158 | } |
---|
159 | } else { |
---|
160 | result.setAction(Action.ERROR); |
---|
161 | } |
---|
162 | } else { |
---|
163 | // unsupported auth method, retry |
---|
164 | result.setAction(Action.RETRY); |
---|
165 | } |
---|
166 | } else { |
---|
167 | result.setAction(Action.ERROR); |
---|
168 | } |
---|
169 | } |
---|
170 | return result; |
---|
171 | } |
---|
172 | |
---|
173 | private String decodeBase64(String s) throws ServletException { |
---|
174 | try { |
---|
175 | byte[] bytes = Base64.decodeBase64(s); |
---|
176 | return new String(bytes, "US-ASCII"); |
---|
177 | } catch (UnsupportedEncodingException e) { |
---|
178 | throw new ServletException("unsupported encoding"); |
---|
179 | } |
---|
180 | } |
---|
181 | |
---|
182 | private boolean checkCredential(String username, String password) { |
---|
183 | Entry entry = null; |
---|
184 | Lock lock = userDbLock.readLock(); |
---|
185 | lock.lock(); |
---|
186 | try { |
---|
187 | entry = userDb.get(username); |
---|
188 | } finally { |
---|
189 | lock.unlock(); |
---|
190 | } |
---|
191 | if (entry != null) { |
---|
192 | return entry.checkPassword(password); |
---|
193 | } |
---|
194 | return false; |
---|
195 | } |
---|
196 | |
---|
197 | private Map<String, String> getAttributes(String username) { |
---|
198 | Entry entry = null; |
---|
199 | Lock lock = userDbLock.readLock(); |
---|
200 | lock.lock(); |
---|
201 | try { |
---|
202 | entry = userDb.get(username); |
---|
203 | } finally { |
---|
204 | lock.unlock(); |
---|
205 | } |
---|
206 | if (entry != null) { |
---|
207 | return entry.getAttributes(); |
---|
208 | } |
---|
209 | return null; |
---|
210 | |
---|
211 | } |
---|
212 | |
---|
213 | private void loadUserDatabase(InputStream in) throws IOException { |
---|
214 | BufferedReader reader = |
---|
215 | new BufferedReader(new InputStreamReader(in, "UTF-8")); |
---|
216 | Lock lock = userDbLock.writeLock(); |
---|
217 | lock.lock(); |
---|
218 | try { |
---|
219 | userDb.clear(); |
---|
220 | |
---|
221 | String line; |
---|
222 | while ((line = reader.readLine()) != null) { |
---|
223 | line = line.trim(); |
---|
224 | if (line.isEmpty() || line.startsWith("#")) { |
---|
225 | continue; |
---|
226 | } |
---|
227 | int pos = line.indexOf(':'); |
---|
228 | if ((pos != -1) && (pos < (line.length() - 2))) { |
---|
229 | String username = line.substring(0, pos).trim(); |
---|
230 | int pos2 = line.indexOf(':', pos + 1); |
---|
231 | String password = null; |
---|
232 | String attrs = null; |
---|
233 | if ((pos2 != -1) && (pos2 < (line.length() -2))) { |
---|
234 | password = line.substring(pos + 1, pos2).trim(); |
---|
235 | attrs = line.substring(pos2 + 1).trim(); |
---|
236 | } else { |
---|
237 | password = line.substring(pos + 1).trim(); |
---|
238 | } |
---|
239 | |
---|
240 | if ((username != null) && (password != null)) { |
---|
241 | if (!username.isEmpty() && !password.isEmpty()) { |
---|
242 | HashMethod method = HashMethod.PLAIN; |
---|
243 | if (password.startsWith("{")) { |
---|
244 | pos = password.indexOf('}', 1); |
---|
245 | if ((pos != -1) && |
---|
246 | (pos < password.length() - 2)) { |
---|
247 | method = HashMethod.fromString(password |
---|
248 | .substring(1, pos)); |
---|
249 | password = password.substring(pos + 1) |
---|
250 | .toLowerCase(); |
---|
251 | } |
---|
252 | } |
---|
253 | if (method != null) { |
---|
254 | Map<String, String> attributes = null; |
---|
255 | if (attrs != null) { |
---|
256 | attributes = parseAttributes(attrs); |
---|
257 | } |
---|
258 | userDb.put(username, new Entry(method, |
---|
259 | username, password, attributes)); |
---|
260 | continue; |
---|
261 | } |
---|
262 | } |
---|
263 | } |
---|
264 | } |
---|
265 | // FIXME: better logging? |
---|
266 | System.err.println("MALFORMED LINE: " + line); |
---|
267 | } |
---|
268 | } finally { |
---|
269 | lock.unlock(); |
---|
270 | } |
---|
271 | reader.close(); |
---|
272 | } |
---|
273 | |
---|
274 | private Map<String, String> parseAttributes(String s) { |
---|
275 | Map<String, String> attributes = null; |
---|
276 | String[] attrs = s.split("\\s*,\\s*"); |
---|
277 | if ((attrs != null) && (attrs.length > 0)) { |
---|
278 | Pattern pattern = Pattern.compile("(\\S+)\\s*=\\s*\"([^\"]+)\""); |
---|
279 | for (String attr : attrs) { |
---|
280 | Matcher m = pattern.matcher(attr); |
---|
281 | if (m.matches()) { |
---|
282 | if (attributes == null) { |
---|
283 | attributes = new HashMap<String,String>(attrs.length); |
---|
284 | } |
---|
285 | attributes.put(m.group(1).toLowerCase(), m.group(2)); |
---|
286 | } else { |
---|
287 | System.err.println("MALFORMED ATTRIBUTE: " + attr); |
---|
288 | } |
---|
289 | } |
---|
290 | } |
---|
291 | return attributes; |
---|
292 | } |
---|
293 | |
---|
294 | } // class BasicAuthStrategy |
---|