/** * Copyright 2012 Universitat Pompeu Fabra. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * */ package org.onexus.resource.manager.internal.ws.git; import org.eclipse.jgit.util.Base64; import org.eclipse.jgit.util.StringUtils; import org.onexus.resource.api.IAuthorizationManager; import org.onexus.resource.api.IProfileManager; import org.onexus.resource.api.IResourceManager; import org.onexus.resource.api.ORI; import org.onexus.resource.api.Project; import org.onexus.resource.api.session.LoginContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.nio.charset.Charset; import java.security.Principal; import java.text.MessageFormat; public class GitSecurityFilter implements Filter { private static final Logger LOGGER = LoggerFactory.getLogger(GitSecurityFilter.class); private static final String BASIC = "Basic"; private static final String CHALLENGE = BASIC + " realm=\"karaf\""; private static final String gitReceivePack = "/git-receive-pack"; private static final String gitUploadPack = "/git-upload-pack"; private static final String[] suffixes = {gitReceivePack, gitUploadPack, "/info/refs", "/HEAD", "/objects"}; private IAuthorizationManager authorizationManager; private IResourceManager resourceManager; private IProfileManager profileManager; @Override public void init(FilterConfig filterConfig) throws ServletException { LOGGER.debug("FilterConfig: " + filterConfig); } @Override public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; String fullUrl = getFullUrl(httpRequest); String repository = extractRepositoryName(fullUrl); String fullSuffix = fullUrl.substring(repository.length()); String requestPrivilege = getUrlRequestAction(fullSuffix); String user = getUser(httpRequest); if (IAuthorizationManager.WRITE.equals(requestPrivilege) && user == null) { httpResponse.setHeader("WWW-Authenticate", CHALLENGE); httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED); return; } // Find a project with this name ORI resource = null; for (Project project : resourceManager.getProjects()) { if (repository.equals(project.getName())) { resource = project.getORI(); break; } } if (resource == null) { httpResponse.sendError(HttpServletResponse.SC_NOT_FOUND); return; } if (!authorizationManager.check(requestPrivilege, resource) && user == null) { httpResponse.setHeader("WWW-Authenticate", CHALLENGE); httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED); return; } Project project = null; try { project = resourceManager.getProject(resource.getProjectUrl()); } catch (Exception e) { LOGGER.error("At git serving '" + resource + "'", e); } //TODO automatic project creation on push // if (project == null && requestPrivilege.equals(IAuthorizationManager.WRITE)) { // // } if (project == null) { httpResponse.sendError(HttpServletResponse.SC_NOT_FOUND); return; } chain.doFilter(new AuthenticatedRequest(user, httpRequest), httpResponse); } protected String getUser(HttpServletRequest httpRequest) { final String authorization = httpRequest.getHeader("Authorization"); if (authorization != null && authorization.startsWith(BASIC)) { // Authorization: Basic base64credentials String base64Credentials = authorization.substring(BASIC.length()).trim(); String credentials = new String(Base64.decode(base64Credentials), Charset.forName("UTF-8")); // credentials = username:password final String[] values = credentials.split(":", 2); if (values.length == 2) { String username = values[0]; String password = values[1]; LoginContext.set(new LoginContext(username), httpRequest.getRequestedSessionId()); boolean valid = false; String[] tokens = profileManager.getValueArray("tokens"); if (tokens != null) { for (String token : tokens) { if (password.equals(token)) { valid = true; break; } } } if (valid) { return username; } LoginContext.get().logout(); } LOGGER.info(MessageFormat.format("AUTH: invalid credentials ({0})", credentials)); } return null; } @Override public void destroy() { } private String getFullUrl(HttpServletRequest httpRequest) { String servletUrl = httpRequest.getContextPath() + httpRequest.getServletPath(); String url = httpRequest.getRequestURI().substring(servletUrl.length()); String params = httpRequest.getQueryString(); if (url.length() > 0 && url.charAt(0) == '/') { url = url.substring(1); } return url + (StringUtils.isEmptyOrNull(params) ? "" : "?" + params); } private String extractRepositoryName(String url) { String repository = url; // get the repository name from the url by finding a known url suffix for (String urlSuffix : suffixes) { if (repository.indexOf(urlSuffix) > -1) { repository = repository.substring(0, repository.indexOf(urlSuffix)); } } return repository; } private String getUrlRequestAction(String suffix) { if (!StringUtils.isEmptyOrNull(suffix)) { if (suffix.startsWith(gitReceivePack)) { return IAuthorizationManager.WRITE; } else if (suffix.startsWith(gitUploadPack)) { return IAuthorizationManager.READ; } else if (suffix.contains("?service=git-receive-pack")) { return IAuthorizationManager.WRITE; } else if (suffix.contains("?service=git-upload-pack")) { return IAuthorizationManager.READ; } else { return IAuthorizationManager.READ; } } return null; } public IAuthorizationManager getAuthorizationManager() { return authorizationManager; } public void setAuthorizationManager(IAuthorizationManager authorizationManager) { this.authorizationManager = authorizationManager; } public IResourceManager getResourceManager() { return resourceManager; } public void setResourceManager(IResourceManager resourceManager) { this.resourceManager = resourceManager; } public IProfileManager getProfileManager() { return profileManager; } public void setProfileManager(IProfileManager profileManager) { this.profileManager = profileManager; } /** * Wraps a standard HttpServletRequest and overrides user principal methods. */ public static class AuthenticatedRequest extends ServletRequestWrapper { private String user; public AuthenticatedRequest(String user, HttpServletRequest req) { super(req); this.user = user; } @Override public String getRemoteUser() { return user; } @Override public Principal getUserPrincipal() { return new Principal() { @Override public String getName() { return user; } }; } } }