diff --git a/scm-web-api/src/main/java/sonia/scm/web/filter/BasicAuthenticationFilter.java b/scm-web-api/src/main/java/sonia/scm/web/filter/BasicAuthenticationFilter.java index ff29c1dc0d..4feb642099 100644 --- a/scm-web-api/src/main/java/sonia/scm/web/filter/BasicAuthenticationFilter.java +++ b/scm-web-api/src/main/java/sonia/scm/web/filter/BasicAuthenticationFilter.java @@ -10,11 +10,12 @@ package sonia.scm.web.filter; //~--- non-JDK imports -------------------------------------------------------- import com.google.inject.Inject; +import com.google.inject.Provider; import com.google.inject.Singleton; import sonia.scm.User; import sonia.scm.util.Util; -import sonia.scm.web.security.Authenticator; +import sonia.scm.web.security.SecurityContext; //~--- JDK imports ------------------------------------------------------------ @@ -56,20 +57,6 @@ public class BasicAuthenticationFilter extends HttpFilter /** Field description */ public static final String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate"; - //~--- constructors --------------------------------------------------------- - - /** - * Constructs ... - * - * - * @param authenticator - */ - @Inject - public BasicAuthenticationFilter(Authenticator authenticator) - { - this.authenticator = authenticator; - } - //~--- methods -------------------------------------------------------------- /** @@ -88,32 +75,40 @@ public class BasicAuthenticationFilter extends HttpFilter HttpServletResponse response, FilterChain chain) throws IOException, ServletException { - User user = authenticator.getUser(request); + SecurityContext securityContext = securityContextProvider.get(); + User user = null; - if (user == null) + if (securityContext != null) { - String authentication = request.getHeader(HEADER_AUTHORIZATION); - - if (Util.isEmpty(authentication)) + if (!securityContext.isAuthenticated()) { - sendUnauthorized(response); + String authentication = request.getHeader(HEADER_AUTHORIZATION); + + if (Util.isEmpty(authentication)) + { + sendUnauthorized(response); + } + else + { + if (!authentication.toUpperCase().startsWith( + AUTHORIZATION_BASIC_PREFIX)) + { + throw new ServletException("wrong basic header"); + } + + String token = authentication.substring(6); + + token = new String(Base64.decode(token.getBytes())); + + String[] credentials = token.split(CREDENTIAL_SEPARATOR); + + user = securityContext.authenticate(request, response, + credentials[0], credentials[1]); + } } else { - if (!authentication.toUpperCase().startsWith( - AUTHORIZATION_BASIC_PREFIX)) - { - throw new ServletException("wrong basic header"); - } - - String token = authentication.substring(6); - - token = new String(Base64.decode(token.getBytes())); - - String[] credentials = token.split(CREDENTIAL_SEPARATOR); - - user = authenticator.authenticate(request, credentials[0], - credentials[1]); + user = securityContext.getUser(); } } @@ -145,5 +140,6 @@ public class BasicAuthenticationFilter extends HttpFilter //~--- fields --------------------------------------------------------------- /** Field description */ - private Authenticator authenticator; + @Inject + private Provider securityContextProvider; } diff --git a/scm-web-api/src/main/java/sonia/scm/web/security/Authenticator.java b/scm-web-api/src/main/java/sonia/scm/web/security/Authenticator.java index f11a822dee..abe9904f85 100644 --- a/scm-web-api/src/main/java/sonia/scm/web/security/Authenticator.java +++ b/scm-web-api/src/main/java/sonia/scm/web/security/Authenticator.java @@ -14,6 +14,7 @@ import sonia.scm.User; //~--- JDK imports ------------------------------------------------------------ import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; /** * @@ -27,23 +28,13 @@ public interface Authenticator * * * @param request + * @param response * @param username * @param password * * @return */ - public User authenticate(HttpServletRequest request, String username, + public User authenticate(HttpServletRequest request, + HttpServletResponse response, String username, String password); - - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @param request - * - * @return - */ - public User getUser(HttpServletRequest request); } diff --git a/scm-web-api/src/main/java/sonia/scm/web/security/BasicSecurityContext.java b/scm-web-api/src/main/java/sonia/scm/web/security/BasicSecurityContext.java new file mode 100644 index 0000000000..1ebe9fd3e6 --- /dev/null +++ b/scm-web-api/src/main/java/sonia/scm/web/security/BasicSecurityContext.java @@ -0,0 +1,85 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + + + +package sonia.scm.web.security; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.inject.Inject; +import com.google.inject.servlet.SessionScoped; + +import sonia.scm.User; + +//~--- JDK imports ------------------------------------------------------------ + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * + * @author Sebastian Sdorra + */ +@SessionScoped +public class BasicSecurityContext implements SecurityContext +{ + + /** + * Method description + * + * + * @param request + * @param response + * @param username + * @param password + * + * @return + */ + @Override + public User authenticate(HttpServletRequest request, + HttpServletResponse response, String username, + String password) + { + user = authenticator.authenticate(request, response, username, password); + + return user; + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + @Override + public User getUser() + { + return user; + } + + /** + * Method description + * + * + * @return + */ + @Override + public boolean isAuthenticated() + { + return user != null; + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + @Inject + private Authenticator authenticator; + + /** Field description */ + private User user; +} diff --git a/scm-web-api/src/main/java/sonia/scm/web/security/SecurityContext.java b/scm-web-api/src/main/java/sonia/scm/web/security/SecurityContext.java new file mode 100644 index 0000000000..0f011bc050 --- /dev/null +++ b/scm-web-api/src/main/java/sonia/scm/web/security/SecurityContext.java @@ -0,0 +1,58 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + + + +package sonia.scm.web.security; + +//~--- non-JDK imports -------------------------------------------------------- + +import sonia.scm.User; + +//~--- JDK imports ------------------------------------------------------------ + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * + * @author Sebastian Sdorra + */ +public interface SecurityContext +{ + + /** + * Method description + * + * + * @param request + * @param response + * @param username + * @param password + * + * @return + */ + public User authenticate(HttpServletRequest request, + HttpServletResponse response, String username, + String password); + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + public User getUser(); + + /** + * Method description + * + * + * @return + */ + public boolean isAuthenticated(); +} diff --git a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java index b7b884875b..e50ef38622 100644 --- a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java +++ b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java @@ -10,21 +10,24 @@ package sonia.scm; //~--- non-JDK imports -------------------------------------------------------- import com.google.inject.multibindings.Multibinder; -import com.google.inject.name.Names; import com.google.inject.servlet.ServletModule; import sonia.scm.api.rest.UriExtensionsConfig; import sonia.scm.cache.CacheManager; import sonia.scm.cache.CacheRepositoryManagerDecorator; import sonia.scm.cache.EhCacheManager; +import sonia.scm.filter.SecurityFilter; import sonia.scm.plugin.SCMPluginManager; import sonia.scm.plugin.ScriptResourceServlet; import sonia.scm.repository.BasicRepositoryManager; import sonia.scm.repository.RepositoryHandler; import sonia.scm.repository.RepositoryManager; +import sonia.scm.util.DebugServlet; import sonia.scm.web.ScmWebPluginContext; import sonia.scm.web.security.Authenticator; +import sonia.scm.web.security.BasicSecurityContext; import sonia.scm.web.security.DemoAuthenticator; +import sonia.scm.web.security.SecurityContext; //~--- JDK imports ------------------------------------------------------------ @@ -47,6 +50,9 @@ import java.util.logging.Logger; public class ScmServletModule extends ServletModule { + /** Field description */ + public static final String PATTERN_DEBUG = "/debug.html"; + /** Field description */ public static final String PATTERN_PAGE = "*.html"; @@ -100,6 +106,8 @@ public class ScmServletModule extends ServletModule SCMContextProvider context = SCMContext.getContext(); bind(SCMContextProvider.class).toInstance(context); + bind(Authenticator.class).to(DemoAuthenticator.class); + bind(SecurityContext.class).to(BasicSecurityContext.class); Multibinder repositoryHandlerBinder = Multibinder.newSetBinder(binder(), RepositoryHandler.class); @@ -123,7 +131,6 @@ public class ScmServletModule extends ServletModule } bind(CacheManager.class).to(EhCacheManager.class); - bind(Authenticator.class).to(DemoAuthenticator.class); bind(RepositoryManager.class).annotatedWith(Undecorated.class).to( BasicRepositoryManager.class); bind(RepositoryManager.class).to(CacheRepositoryManagerDecorator.class); @@ -135,6 +142,10 @@ public class ScmServletModule extends ServletModule * filter(PATTERN_PAGE, PATTERN_COMPRESSABLE).through(GZipFilter.class); * filter(PATTERN_RESTAPI).through(SecurityFilter.class); */ + filter(PATTERN_RESTAPI, PATTERN_DEBUG).through(SecurityFilter.class); + + // debug servlet + serve(PATTERN_DEBUG).with(DebugServlet.class); // plugin resources serve(PATTERN_PLUGIN_SCRIPT).with(ScriptResourceServlet.class); diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AuthenticationResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AuthenticationResource.java index 6d8660e916..1327c5947d 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AuthenticationResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AuthenticationResource.java @@ -16,11 +16,12 @@ import sonia.scm.ScmState; import sonia.scm.User; import sonia.scm.repository.RepositoryManager; import sonia.scm.repository.RepositoryType; -import sonia.scm.web.security.Authenticator; +import sonia.scm.web.security.SecurityContext; //~--- JDK imports ------------------------------------------------------------ import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import javax.ws.rs.FormParam; import javax.ws.rs.GET; @@ -47,6 +48,7 @@ public class AuthenticationResource * * * @param request + * @param response * @param username * @param password * @@ -54,11 +56,13 @@ public class AuthenticationResource */ @POST public ScmState getState(@Context HttpServletRequest request, + @Context HttpServletResponse response, @FormParam("username") String username, @FormParam("password") String password) { ScmState state = null; - User user = authenticator.authenticate(request, username, password); + User user = securityContext.authenticate(request, response, username, + password); if (user != null) { @@ -84,7 +88,7 @@ public class AuthenticationResource public ScmState getState(@Context HttpServletRequest request) { ScmState state = null; - User user = authenticator.getUser(request); + User user = securityContext.getUser(); if (user != null) { @@ -122,9 +126,9 @@ public class AuthenticationResource /** Field description */ @Inject - private Authenticator authenticator; + private RepositoryManager repositoryManger; /** Field description */ @Inject - private RepositoryManager repositoryManger; + private SecurityContext securityContext; } diff --git a/scm-webapp/src/main/java/sonia/scm/filter/SecurityFilter.java b/scm-webapp/src/main/java/sonia/scm/filter/SecurityFilter.java index c23bbf81b0..c1f766a683 100644 --- a/scm-webapp/src/main/java/sonia/scm/filter/SecurityFilter.java +++ b/scm-webapp/src/main/java/sonia/scm/filter/SecurityFilter.java @@ -10,23 +10,20 @@ package sonia.scm.filter; //~--- non-JDK imports -------------------------------------------------------- import com.google.inject.Inject; +import com.google.inject.Provider; import com.google.inject.Singleton; -import sonia.scm.User; import sonia.scm.web.filter.HttpFilter; import sonia.scm.web.filter.SecurityHttpServletRequestWrapper; -import sonia.scm.web.security.Authenticator; +import sonia.scm.web.security.SecurityContext; //~--- JDK imports ------------------------------------------------------------ import java.io.IOException; -import java.security.Principal; - import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletResponse; /** @@ -58,26 +55,33 @@ public class SecurityFilter extends HttpFilter HttpServletResponse response, FilterChain chain) throws IOException, ServletException { - String uri = - request.getRequestURI().substring(request.getContextPath().length()); + SecurityContext securityContext = securityContextProvider.get(); - if (!uri.startsWith(URL_AUTHENTICATION)) + if (securityContext != null) { - User user = authenticator.getUser(request); + String uri = + request.getRequestURI().substring(request.getContextPath().length()); - if (user != null) + if (!uri.startsWith(URL_AUTHENTICATION)) { - chain.doFilter(new SecurityHttpServletRequestWrapper(request, user), - response); + if (securityContext.isAuthenticated()) + { + chain.doFilter(new SecurityHttpServletRequestWrapper(request, + securityContext.getUser()), response); + } + else + { + response.sendError(HttpServletResponse.SC_UNAUTHORIZED); + } } else { - response.sendError(HttpServletResponse.SC_UNAUTHORIZED); + chain.doFilter(request, response); } } else { - chain.doFilter(request, response); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED); } } @@ -85,5 +89,5 @@ public class SecurityFilter extends HttpFilter /** Field description */ @Inject - private Authenticator authenticator; + private Provider securityContextProvider; } diff --git a/scm-webapp/src/main/java/sonia/scm/util/DebugServlet.java b/scm-webapp/src/main/java/sonia/scm/util/DebugServlet.java new file mode 100644 index 0000000000..8bc957a468 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/util/DebugServlet.java @@ -0,0 +1,245 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + + + +package sonia.scm.util; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.inject.Singleton; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.IOException; +import java.io.PrintWriter; + +import java.util.Enumeration; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +/** + * + * @author Sebastian Sdorra + */ +@Singleton +public class DebugServlet extends HttpServlet +{ + + /** Field description */ + private static final long serialVersionUID = -1918351712617918728L; + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param request + * @param response + * + * @throws IOException + * @throws ServletException + */ + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + processRequest(request, response); + } + + /** + * Method description + * + * + * @param request + * @param response + * + * @throws IOException + * @throws ServletException + */ + @Override + protected void doPost(HttpServletRequest request, + HttpServletResponse response) + throws ServletException, IOException + { + processRequest(request, response); + } + + /** + * Method description + * + * + * @param writer + * @param context + */ + private void appendAttributes(PrintWriter writer, AttributeContext context) + { + writer.append("
    "); + + Enumeration enm = context.getNames(); + + while (enm.hasMoreElements()) + { + String key = (String) enm.nextElement(); + + writer.append("
  • ").append(key).append(" = "); + + Object value = context.getValue(key); + + writer.append("(").append(value.getClass().toString()).append(") "); + writer.append(value.toString()).append("
  • "); + } + + writer.append("
"); + } + + /** + * Method description + * + * + * @param writer + */ + private void appendContextAttributes(PrintWriter writer) + { + writer.append("

ServletContext Attributes

"); + + final ServletContext context = getServletContext(); + + appendAttributes(writer, new AttributeContext() + { + @Override + public Enumeration getNames() + { + return context.getAttributeNames(); + } + @Override + public Object getValue(String name) + { + return context.getAttribute(name); + } + }); + } + + /** + * Method description + * + * + * @param writer + * @param session + */ + private void appendSessionAttributes(PrintWriter writer, + final HttpSession session) + { + writer.append("

Session Attributes

"); + appendAttributes(writer, new AttributeContext() + { + @Override + public Enumeration getNames() + { + return session.getAttributeNames(); + } + @Override + public Object getValue(String name) + { + return session.getAttribute(name); + } + }); + } + + /** + * Method description + * + * + * @param writer + */ + private void printFooter(PrintWriter writer) + { + writer.append(""); + } + + /** + * Method description + * + * + * @param writer + */ + private void printHeader(PrintWriter writer) + { + writer.append(""); + writer.append("SCM Manaer :: Debugging"); + writer.append("

SCM Manaer :: Debugging

"); + } + + /** + * Method description + * + * + * @param request + * @param response + * + * @throws IOException + * @throws ServletException + */ + private void processRequest(HttpServletRequest request, + HttpServletResponse response) + throws ServletException, IOException + { + PrintWriter writer = null; + + try + { + response.setContentType("text/html"); + writer = response.getWriter(); + printHeader(writer); + appendContextAttributes(writer); + + HttpSession session = request.getSession(); + + appendSessionAttributes(writer, session); + printFooter(writer); + } + finally + { + writer.close(); + } + } + + //~--- inner interfaces ----------------------------------------------------- + + /** + * Interface description + * + * + * @version Enter version here..., 10/10/15 + * @author Enter your name here... + */ + private interface AttributeContext + { + + /** + * Method description + * + * + * @return + */ + public Enumeration getNames(); + + /** + * Method description + * + * + * @param name + * + * @return + */ + public Object getValue(String name); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/web/security/DemoAuthenticator.java b/scm-webapp/src/main/java/sonia/scm/web/security/DemoAuthenticator.java index c0c13329f8..4657d4b76d 100644 --- a/scm-webapp/src/main/java/sonia/scm/web/security/DemoAuthenticator.java +++ b/scm-webapp/src/main/java/sonia/scm/web/security/DemoAuthenticator.java @@ -14,7 +14,7 @@ import sonia.scm.User; //~--- JDK imports ------------------------------------------------------------ import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpServletResponse; /** * @@ -42,13 +42,15 @@ public class DemoAuthenticator implements Authenticator * * * @param request + * @param response * @param username * @param password * * @return */ @Override - public User authenticate(HttpServletRequest request, String username, + public User authenticate(HttpServletRequest request, + HttpServletResponse response, String username, String password) { User user = null; @@ -56,34 +58,6 @@ public class DemoAuthenticator implements Authenticator if (DEMO_USERNAME.equals(username) && DEMO_PASSWORD.equals(password)) { user = new User(username, DEMO_DISPLAYNAME, DEMO_MAIL); - - HttpSession session = request.getSession(true); - - session.setAttribute(DemoAuthenticator.class.getName(), user); - } - - return user; - } - - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @param request - * - * @return - */ - @Override - public User getUser(HttpServletRequest request) - { - User user = null; - HttpSession session = request.getSession(); - - if (session != null) - { - user = (User) session.getAttribute(DemoAuthenticator.class.getName()); } return user;