001: /**********************************************************************************
002: * $URL: https://source.sakaiproject.org/svn/user/tags/sakai_2-4-1/user-util/util/src/java/org/sakaiproject/util/BasicAuth.java $
003: * $Id: BasicAuth.java 22834 2007-03-17 23:27:41Z ggolden@umich.edu $
004: ***********************************************************************************
005: *
006: * Copyright (c) 2006, 2007 The Sakai Foundation.
007: *
008: * Licensed under the Educational Community License, Version 1.0 (the "License");
009: * you may not use this file except in compliance with the License.
010: * You may obtain a copy of the License at
011: *
012: * http://www.opensource.org/licenses/ecl1.php
013: *
014: * Unless required by applicable law or agreed to in writing, software
015: * distributed under the License is distributed on an "AS IS" BASIS,
016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: * See the License for the specific language governing permissions and
018: * limitations under the License.
019: *
020: **********************************************************************************/package org.sakaiproject.util;
021:
022: import java.io.IOException;
023: import java.util.ArrayList;
024: import java.util.regex.Matcher;
025: import java.util.regex.Pattern;
026:
027: import javax.servlet.http.HttpServletRequest;
028: import javax.servlet.http.HttpServletResponse;
029:
030: import org.sakaiproject.user.api.Authentication;
031: import org.sakaiproject.user.api.AuthenticationException;
032: import org.sakaiproject.user.api.Evidence;
033: import org.sakaiproject.user.cover.AuthenticationManager;
034: import org.sakaiproject.component.cover.ServerConfigurationService;
035: import org.sakaiproject.event.cover.UsageSessionService;
036:
037: import sun.misc.BASE64Decoder;
038:
039: /**
040: * This is implemented in a filter, since most httpclients (ie non browser
041: * clients) dont know what to do with a redirect.
042: *
043: * There are 2 mechanisms for selecting basic authentication. 1. The client is
044: * not a browser as reported by the BasicAuthFilter.isBrowser method. 2. The
045: * user requested basic auth in the URL and the
046: * BasicAuthFilter.requestedBasicAuth confirms this.
047: *
048: * in sakai.properties if allowbasicauth.login = true, then this feature is
049: * enabled in BasicAuthFilter, the determination of non browser clients is
050: * driven by matching user agent headers against a sequence of regex patterns.
051: * These are defined in BasicAuthFilter with the form if the pattern matches a
052: * browser 1pattern or if it does not match 0pattern
053: *
054: * Addtional patterns may be added to sakai.properties as a multiple string
055: * property against login.browser.user.agent
056: *
057: * The list is matched in order, the first match found being definitive. If no
058: * match is found, then the client is assumed to be a browser.
059: *
060: * e.g. if itunes was not listed as a client, either:
061: *
062: * Add
063: *
064: * login.browser.user.agent.count=1
065: * login.browser.user.agent.1=0itunes.*
066: *
067: * to sakai.properties, or
068: *
069: * Add __auth=basic to the end of the url, e.g.
070: *
071: * http://localhost:8080/access/wiki/123-1231-32123-132123/-.20.rss?someparam=someval&__auth=basic
072: *
073: * This string is available in BasicAuthFilter.BASIC_AUTH_LOGIN_REQUEST
074: *
075: */
076:
077: public class BasicAuth {
078:
079: /**
080: * The query parameter and value that indicates the request will want basic
081: * auth if required
082: */
083: public static final String BASIC_AUTH_LOGIN_REQUEST = "__auth=basic";
084:
085: public static Pattern[] patterns = null;
086:
087: private static String[] match;
088:
089: /**
090: * The default set of UserAgent patterns to force basic auth with
091: */
092: private static String[] matchPatterns = { "0.*Thunderbird.*",
093: "1Mozilla.*", "0i[tT]unes.*",
094: "0Jakarta Commons-HttpClient.*", "0.*Googlebot/2.1.*",
095: "0[gG]oogle[bB]ot.*", "0curl.*" };
096:
097: /**
098: * Initialise the patterns, since some of the spring stuf may not be up when
099: * the bean is created, this is here to make certain that init is performed
100: * when spring is ready
101: *
102: */
103: public void init() {
104: ArrayList pat = new ArrayList();
105: ArrayList mat = new ArrayList();
106: String[] morepatterns = null;
107: try {
108: morepatterns = ServerConfigurationService
109: .getStrings("login.browser.user.agent");
110: } catch (Exception ex) {
111:
112: }
113: if (morepatterns != null) {
114: for (int i = 0; i < morepatterns.length; i++) {
115: String line = morepatterns[i];
116: String check = line.substring(0, 1);
117: mat.add(check);
118: line = line.substring(1);
119: pat.add(Pattern.compile(line));
120: }
121: }
122: for (int i = 0; i < matchPatterns.length; i++) {
123: String line = matchPatterns[i];
124: String check = line.substring(0, 1);
125: mat.add(check);
126: line = line.substring(1);
127: pat.add(Pattern.compile(line));
128: }
129:
130: patterns = new Pattern[pat.size()];
131: patterns = (Pattern[]) pat.toArray(patterns);
132: match = new String[mat.size()];
133: match = (String[]) mat.toArray(match);
134: }
135:
136: /**
137: * If this method returns true, the user agent is a browser
138: *
139: * @param header
140: * @return
141: */
142: protected boolean isBrowser(String userAgentHeader) {
143: if (userAgentHeader == null)
144: return false;
145:
146: if (patterns != null) {
147: for (int i = 0; i < patterns.length; i++) {
148: Matcher m = patterns[i].matcher(userAgentHeader);
149: if (m.matches()) {
150: return "1".equals(match[i]);
151: }
152: }
153: return true;
154: }
155: return true;
156: }
157:
158: /**
159: * This method looks at the returnUrl and if there is a request parameter in
160: * the URL requesting basic authentication, this method returns true
161: *
162: * @param returnUrl
163: * @return
164: */
165: protected boolean requestedBasicAuth(HttpServletRequest request) {
166: String queryString = request.getQueryString();
167: if (queryString == null) {
168: return false;
169: } else {
170: boolean ret = (queryString
171: .indexOf(BASIC_AUTH_LOGIN_REQUEST) != -1);
172: return ret;
173: }
174: }
175:
176: /**
177: * Should a basic auth be used
178: * @param req
179: * @return
180: */
181: protected boolean doBasicAuth(HttpServletRequest req) {
182: boolean allowBasicAuth = ServerConfigurationService.getBoolean(
183: "allow.basic.auth.login", false);
184:
185: if (allowBasicAuth) {
186: if (requestedBasicAuth(req)
187: || !isBrowser(req.getHeader("User-Agent"))) {
188: allowBasicAuth = true;
189: } else {
190: allowBasicAuth = false;
191: }
192: }
193:
194: return allowBasicAuth;
195: }
196:
197: /**
198: * Perform a login based on the headers, if they are not present or it fails do nothing
199: * @param req
200: * @return
201: * @throws IOException
202: */
203: public boolean doLogin(HttpServletRequest req) throws IOException {
204:
205: if (doBasicAuth(req)) {
206:
207: String auth;
208: auth = req.getHeader("Authorization");
209:
210: Evidence e = null;
211: try {
212: if (auth != null) {
213: auth = auth.trim();
214: if (auth.startsWith("Basic ")) {
215: auth = auth.substring(6).trim();
216: BASE64Decoder denc = new BASE64Decoder();
217: auth = new String(denc.decodeBuffer(auth));
218: int colon = auth.indexOf(":");
219: if (colon != -1) {
220: String eid = auth.substring(0, colon);
221: String pw = auth.substring(colon + 1);
222: if (eid.length() > 0 && pw.length() > 0) {
223: e = new IdPwEvidence(eid, pw);
224: }
225: }
226: }
227: }
228: } catch (Throwable ex) {
229: ex.printStackTrace();
230: }
231:
232: // authenticate
233: try {
234: if (e == null) {
235: throw new AuthenticationException(
236: "missing required fields");
237: }
238:
239: Authentication a = AuthenticationManager
240: .authenticate(e);
241:
242: // login the user
243: if (UsageSessionService.login(a, req)) {
244: return true;
245: } else {
246: return false;
247: }
248: } catch (AuthenticationException ex) {
249: // ex.printStackTrace();
250: return false;
251: }
252: }
253: return true;
254: }
255:
256: /**
257: * Emit the basic auth headers and a 401
258: * @param req
259: * @param res
260: * @return
261: * @throws IOException
262: */
263: public boolean doAuth(HttpServletRequest req,
264: HttpServletResponse res) throws IOException {
265: if (doBasicAuth(req)) {
266: String uiService = ServerConfigurationService
267: .getString("ui.service");
268: res.addHeader("WWW-Authenticate", "Basic realm=\""
269: + uiService + "\"");
270: res.sendError(HttpServletResponse.SC_UNAUTHORIZED,
271: "Authorization Required");
272: return true;
273: }
274: return false;
275:
276: }
277:
278: }
|