1
18 package net.bull.javamelody.internal.web;
19
20 import java.io.IOException;
21 import java.security.NoSuchAlgorithmException;
22 import java.util.ArrayList;
23 import java.util.Date;
24 import java.util.List;
25 import java.util.Locale;
26 import java.util.concurrent.atomic.AtomicInteger;
27 import java.util.regex.Pattern;
28
29 import javax.servlet.http.HttpServletRequest;
30 import javax.servlet.http.HttpServletResponse;
31
32 import net.bull.javamelody.Parameter;
33 import net.bull.javamelody.internal.common.LOG;
34 import net.bull.javamelody.internal.common.MessageDigestPasswordEncoder;
35 import net.bull.javamelody.internal.model.Base64Coder;
36
37
41 public class HttpAuth {
42 private static final long AUTH_FAILURES_MAX = 10;
43
44 private static final long LOCK_DURATION = 60L * 60 * 1000;
45
46 private final Pattern allowedAddrPattern;
47
50 private final List<String> authorizedUsers;
51
52 private final AtomicInteger authFailuresCount = new AtomicInteger();
53
54 private Date firstFailureDate;
55
56 public HttpAuth() {
57 super();
58 this.allowedAddrPattern = getAllowedAddrPattern();
59 this.authorizedUsers = getAuthorizedUsers();
60 }
61
62 private static Pattern getAllowedAddrPattern() {
63 if (Parameter.ALLOWED_ADDR_PATTERN.getValue() != null) {
64 return Pattern.compile(Parameter.ALLOWED_ADDR_PATTERN.getValue());
65 }
66 return null;
67 }
68
69 private static List<String> getAuthorizedUsers() {
70
71 final String authUsersInParam = Parameter.AUTHORIZED_USERS.getValue();
72 if (authUsersInParam != null && !authUsersInParam.trim().isEmpty()) {
73 final List<String> authorizedUsers = new ArrayList<>();
74
75 for (final String authUser : authUsersInParam.split("[\n,]")) {
76 final String authUserTrim = authUser.trim();
77 if (!authUserTrim.isEmpty()) {
78 authorizedUsers.add(authUserTrim);
79 LOG.debug("Authorized user: " + authUserTrim.split(":", 2)[0]);
80 }
81 }
82 return authorizedUsers;
83 }
84 return null;
85 }
86
87 public boolean isAllowed(HttpServletRequest httpRequest, HttpServletResponse httpResponse)
88 throws IOException {
89 if (!isRequestAllowed(httpRequest)) {
90 LOG.debug("Forbidden access to monitoring from " + httpRequest.getRemoteAddr());
91 httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN, "Forbidden access");
92 return false;
93 }
94 if (!isUserAuthorized(httpRequest)) {
95
96 httpResponse.setHeader("WWW-Authenticate", "BASIC realm=\"JavaMelody\"");
97 if (isLocked()) {
98 httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED,
99 "Unauthorized (locked)");
100 } else {
101 httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
102 }
103 return false;
104 }
105 return true;
106 }
107
108 private boolean isRequestAllowed(HttpServletRequest httpRequest) {
109 return allowedAddrPattern == null
110 || allowedAddrPattern.matcher(httpRequest.getRemoteAddr()).matches();
111 }
112
113
119 private boolean isUserAuthorized(HttpServletRequest httpRequest) throws IOException {
120 if (authorizedUsers == null) {
121 return true;
122 }
123
124 final String auth = httpRequest.getHeader("Authorization");
125 if (auth == null) {
126 return false;
127 }
128 if (!auth.toUpperCase(Locale.ENGLISH).startsWith("BASIC ")) {
129 return false;
130 }
131
132 final String userpassBase64 = auth.substring("BASIC ".length());
133
134 final String userpass = Base64Coder.decodeString(userpassBase64);
135
136 boolean authOk = false;
137 for (final String authorizedUser : authorizedUsers) {
138
139 final String userpassEncoded = getUserPasswordEncoded(userpass, authorizedUser);
140 if (userpassEncoded != null) {
141
142 if (authorizedUser.equals(userpassEncoded)) {
143 authOk = true;
144 break;
145 }
146 continue;
147 }
148
149 if (authorizedUser.equals(userpass)) {
150 authOk = true;
151 break;
152 }
153 }
154 return checkLockAgainstBruteForceAttack(authOk);
155 }
156
157 private String getUserPasswordEncoded(String userpassDecoded, String authorizedUser)
158 throws IOException {
159 final int indexOfStart = authorizedUser.indexOf(":{");
160 if (indexOfStart != -1) {
161 final int indexOfEnd = authorizedUser.indexOf('}', indexOfStart);
162 if (indexOfEnd != -1) {
163 final String algorithm = authorizedUser.substring(indexOfStart + 2, indexOfEnd);
164 final int indexOfColon = userpassDecoded.indexOf(':');
165 if (indexOfColon != -1) {
166
167 final String pass = userpassDecoded.substring(indexOfColon + 1);
168 return userpassDecoded.substring(0, indexOfColon + 1)
169 + encodePassword(algorithm, pass);
170 }
171 }
172 }
173 return null;
174 }
175
176 private String encodePassword(String algorithm, String password) throws IOException {
177 try {
178 return new MessageDigestPasswordEncoder(algorithm).encodePassword(password);
179 } catch (final NoSuchAlgorithmException e) {
180
181
182
183 throw new IOException(e);
184 }
185 }
186
187 private boolean checkLockAgainstBruteForceAttack(boolean authOk) {
188 if (firstFailureDate == null) {
189 if (!authOk) {
190
191 firstFailureDate = new Date();
192 authFailuresCount.set(1);
193 }
194 } else {
195 if (isLocked()) {
196
197 if (System.currentTimeMillis() - firstFailureDate.getTime() < LOCK_DURATION) {
198 return false;
199 }
200
201 firstFailureDate = null;
202 authFailuresCount.set(0);
203 return checkLockAgainstBruteForceAttack(authOk);
204 }
205 if (authOk) {
206
207 firstFailureDate = null;
208 authFailuresCount.set(0);
209 } else {
210
211 authFailuresCount.incrementAndGet();
212 }
213 }
214 return authOk;
215 }
216
217 private boolean isLocked() {
218 return authFailuresCount.get() > AUTH_FAILURES_MAX;
219 }
220 }
221