Index: project.xml =================================================================== --- project.xml (revision 6878) +++ project.xml (working copy) @@ -85,8 +85,16 @@ sakai-util-text ${sakai.version} - + + + sakaiproject + sakai-authentication + ${sakai.version} + + + + servletapi servletapi 2.4 Index: src/java/org/sakaiproject/tool/access/BasicAuth.java =================================================================== --- src/java/org/sakaiproject/tool/access/BasicAuth.java (revision 0) +++ src/java/org/sakaiproject/tool/access/BasicAuth.java (revision 0) @@ -0,0 +1,257 @@ +package org.sakaiproject.tool.access; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.sakaiproject.api.common.authentication.Authentication; +import org.sakaiproject.api.common.authentication.AuthenticationException; +import org.sakaiproject.api.common.authentication.Evidence; +import org.sakaiproject.api.common.authentication.cover.AuthenticationManager; +import org.sakaiproject.service.framework.config.cover.ServerConfigurationService; +import org.sakaiproject.util.IdPwEvidence; +import org.sakaiproject.util.LoginUtil; + +import sun.misc.BASE64Decoder; + +/** + * This is implemented in a filter, since most httpclients (ie non browser + * clients) dont know what to do with a redirect. + * + * There are 2 mechanisms for selecting basic authentcation. 1. The client is + * not a browser as reported by the BasicAuthFilter.isBrowser method. 2. The + * user requested basic auth in the URL and the + * BasicAuthFilter.requestedBasicAuth confirms this. + * + * in sakai.properties if allowbasicauth.login = true, then this feature is + * enabled in BasicAuthFilter, the determination of non browser clients is + * driven by matching user agent headers against a sequence of regex patterns. + * These are defined in BasicAuthFilter with the form if the pattern matches a + * browser 1pattern or if it does not match 0pattern + * + * Addtional patterns may be added to sakai.properties as a muliple string + * property against login.browser.user.agent + * + * The list is matched in order, the first match found being definative. If no + * match is found, then the client is assumed to be a browser. + * + * + * eg if itunes was not listed as a client. 1. add + * login.browser.user.agent.count=1 login.browser.user.agent.1=0itunes.* + * + * to sakai.properties + * + * + * or 2. Add the __basicauth=02122 to the end of the url eg + * http://localhost:8080/access/wiki/123-1231-32123-132123/-.20.rss?someparam=someval&__basicauth=1 + * + * This is available in BasicAuthFilter.BASIC_AUTH_LOGIN_REQUEST + * + * + */ + +public class BasicAuth { + + /** + * The quesry parameter and value that indicates the request will want basic + * auth if required + */ + public static final String BASIC_AUTH_LOGIN_REQUEST = "__basicauth=1"; + + public static Pattern[] patterns = null; + + private static String[] match; + + /** + * The default set of UserAgent patterns to force basic auth with + */ + private static String[] matchPatterns = { "1Mozilla.*", "0i[tT]unes.*", + "0Jakarta Commons-HttpClient.*", "0.*Googlebot/2.1.*", + "0[gG]oogle[bB]ot.*", "0curl.*" + + }; + + /** + * Initialise the patterns, since some of the spring stuf may not be up when + * the bean is created, this is here to make certain that init is performed + * when spring is ready + * + */ + public void init() { + ArrayList pat = new ArrayList(); + ArrayList mat = new ArrayList(); + String[] morepatterns = null; + try { + morepatterns = ServerConfigurationService + .getStrings("login.browser.user.agent"); + } catch (Exception ex) { + + } + if (morepatterns != null) { + for (int i = 0; i < morepatterns.length; i++) { + String line = morepatterns[i]; + String check = line.substring(0, 1); + mat.add(check); + line = line.substring(1); + pat.add(Pattern.compile(line)); + + } + } + for (int i = 0; i < matchPatterns.length; i++) { + String line = matchPatterns[i]; + String check = line.substring(0, 1); + mat.add(check); + line = line.substring(1); + pat.add(Pattern.compile(line)); + } + + patterns = new Pattern[pat.size()]; + patterns = (Pattern[]) pat.toArray(patterns); + match = new String[mat.size()]; + match = (String[]) mat.toArray(match); + } + + /** + * If this method returns true, the user agent is a browser + * + * @param header + * @return + */ + protected boolean isBrowser(String userAgentHeader) { + if (patterns != null) { + for (int i = 0; i < patterns.length; i++) { + Matcher m = patterns[i].matcher(userAgentHeader); + if (m.matches()) { + // System.err.println("Matched + // "+match[i]+":"+patterns[i].pattern()); + return "1".equals(match[i]); + } + } + return true; + } + return true; + } + + /** + * This method looks at the returnUrl and if there is a request parameter in + * the URL requesting basic authentication, this method returns true + * + * @param returnUrl + * @return + */ + protected boolean requestedBasicAuth(HttpServletRequest request) { + String queryString = request.getQueryString(); + if (queryString == null) { + return false; + } else { + boolean ret = (queryString.indexOf(BASIC_AUTH_LOGIN_REQUEST) != -1); + // System.err.println(" Query String " + queryString + " said " + + // ret); + return ret; + } + } + + /** + * Should a basic auth be used + * @param req + * @return + */ + protected boolean doBasicAuth(HttpServletRequest req) { + boolean allowBasicAuth = ServerConfigurationService.getBoolean( + "allow.basic.auth.login", false); + + if (allowBasicAuth) { + // System.err.println("Basic Auth Checking A"); + if (requestedBasicAuth(req) + || !isBrowser(req.getHeader("User-Agent"))) { + allowBasicAuth = true; + + } else { + allowBasicAuth = false; + + } + } + + return allowBasicAuth; + + } + + /** + * Perform a login based on the headers, if they are not present or it fails do nothing + * @param req + * @return + * @throws IOException + */ + public boolean doLogin(HttpServletRequest req) throws IOException { + + if (doBasicAuth(req)) { + // System.err.println("Basic Auth enabled "); + + String auth = req.getHeader("Authorization"); + Evidence e = null; + try { + if (auth != null) { + auth = auth.trim(); + if (auth.startsWith("Basic ")) { + auth = auth.substring(6).trim(); + BASE64Decoder denc = new BASE64Decoder(); + auth = new String(denc.decodeBuffer(auth)); + int colon = auth.indexOf(":"); + if (colon != -1) { + String eid = auth.substring(0, colon); + String pw = auth.substring(colon + 1); + if (eid.length() > 0 && pw.length() > 0) { + e = new IdPwEvidence(eid, pw); + } + } + } + } + } catch (Exception ex) { + + } + + // authenticate + try { + if (e == null) { + throw new AuthenticationException("missing required fields"); + } + + Authentication a = AuthenticationManager.authenticate(e); + + // login the user + if (LoginUtil.login(a, req)) { + return true; + } else { + return false; + } + } catch (AuthenticationException ex) { + return false; + } + } + return true; + } + + /** + * Emit the basic auth headers and a 401 + * @param req + * @param res + * @return + * @throws IOException + */ + public boolean doAuth(HttpServletRequest req, HttpServletResponse res) + throws IOException { + if (doBasicAuth(req)) { + res.addHeader("WWW-Authenticate", "Basic realm=\"Sakai\""); + res.sendError(HttpServletResponse.SC_UNAUTHORIZED, + "Authorization Required"); + return true; + } + return false; + + } + +} Property changes on: src/java/org/sakaiproject/tool/access/BasicAuth.java ___________________________________________________________________ Name: svn:keywords + Date Revision Author HeadURL Id Name: svn:eol-style + native Index: src/java/org/sakaiproject/tool/access/AccessServlet.java =================================================================== --- src/java/org/sakaiproject/tool/access/AccessServlet.java (revision 6878) +++ src/java/org/sakaiproject/tool/access/AccessServlet.java (working copy) @@ -106,6 +106,8 @@ /** Session attribute holding copyright-accepted references (a collection of Strings). */ protected static final String COPYRIGHT_ACCEPTED_REFS_ATTR = "Access.Copyright.Accepted"; + + protected BasicAuth basicAuth = null; /** init thread - so we don't wait in the actual init() call */ public class AccessServletInit extends Thread @@ -140,6 +142,9 @@ { super.init(config); startInit(); + basicAuth = new BasicAuth(); + basicAuth.init(); + } /** @@ -164,14 +169,15 @@ */ public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { + // process any login that might be present + basicAuth.doLogin(req); // catch the login helper requests String option = req.getPathInfo(); String[] parts = option.split("/"); if ((parts.length == 2) && ((parts[1].equals("login")))) { doLogin(req, res, null); - } - + } else { dispatch(req, res); @@ -192,6 +198,8 @@ */ public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { + // process any login that might be present + basicAuth.doLogin(req); // catch the login helper posts String option = req.getPathInfo(); String[] parts = option.split("/"); @@ -199,7 +207,7 @@ { doLogin(req, res, null); } - + else { sendError(res, HttpServletResponse.SC_NOT_FOUND); @@ -328,7 +336,9 @@ // if not permitted, and the user is the anon user, let them login if (SessionManager.getCurrentSessionUserId() == null) { - doLogin(req, res, origPath); + try { + doLogin(req, res, origPath); + } catch ( IOException ioex ) {} return; } @@ -412,9 +422,17 @@ * HttpServletResponse object back to the client. * @param path * The current request path, set ONLY if we want this to be where to redirect the user after successfull login + * @throws IOException */ - protected void doLogin(HttpServletRequest req, HttpServletResponse res, String path) throws ToolException + protected void doLogin(HttpServletRequest req, HttpServletResponse res, String path) throws ToolException, IOException { + // if basic auth is valid do that + if ( basicAuth.doAuth(req,res) ) { + //System.err.println("BASIC Auth Request Sent to the Browser "); + return; + } + + // get the Sakai session Session session = SessionManager.getCurrentSession(); @@ -423,6 +441,7 @@ { // where to go after session.setAttribute(Tool.HELPER_DONE_URL, Web.returnUrl(req, path)); + } // check that we have a return path set; might have been done earlier @@ -435,6 +454,7 @@ ActiveTool tool = ActiveToolManager.getActiveTool("sakai.login"); String context = req.getContextPath() + req.getServletPath() + "/login"; tool.help(req, res, context, "/login"); + } /** create the info */