package org.openstack.atlas.api.mgmt.filters; import org.openstack.atlas.api.mgmt.filters.helpers.UserEntry; import org.openstack.atlas.util.simplecache.CacheEntry; import org.openstack.atlas.util.simplecache.SimpleCache; import org.openstack.atlas.docs.loadbalancers.api.v1.faults.BadRequest; import org.openstack.atlas.api.filters.helpers.AcceptTypes; import org.openstack.atlas.api.filters.wrappers.HeadersRequestWrapper; import org.openstack.atlas.api.mgmt.filters.helpers.HttpHeadersTools; import org.openstack.atlas.api.mgmt.filters.helpers.XmlJsonConfig; import org.openstack.atlas.api.mgmt.helpers.LDAPTools.MossoAuth; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import javax.naming.NamingException; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Enumeration; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import static org.openstack.atlas.api.filters.helpers.StringUtilities.getExtendedStackTrace; public class ManageAuthFilter implements Filter { private final Log LOG = LogFactory.getLog(ManageAuthFilter.class); private static final String XML = "application/xml"; private static final String JSON = "application/json"; private MossoAuth mossoAuth; private FilterConfig config = null; private XmlJsonConfig xmlJsonConfig; private SimpleCache<UserEntry> ldapCache; private static final BadRequest unAuthorized; private static final BadRequest requiresAuth; private static final BadRequest invalidAuth; private static final int SC_UNAUTHORIZED = HttpServletResponse.SC_UNAUTHORIZED; private static final String LDAPGROUPS = "LDAPGroups"; private static final String LDAPUSER = "LDAPUser"; private static final Pattern jsonUriPattern = Pattern.compile(".*\\.json$", Pattern.CASE_INSENSITIVE); private static final Pattern xmlUriPattern = Pattern.compile(".*\\.xml$", Pattern.CASE_INSENSITIVE); static { invalidAuth = new BadRequest(); invalidAuth.setCode(SC_UNAUTHORIZED); invalidAuth.setMessage("Your Authroization header was improperly formated"); requiresAuth = new BadRequest(); requiresAuth.setCode(SC_UNAUTHORIZED); requiresAuth.setMessage("You must use BASIC HTTP auth"); unAuthorized = new BadRequest(); unAuthorized.setCode(SC_UNAUTHORIZED); unAuthorized.setMessage("eDir bind failed"); } @Override public void init(FilterConfig fc) throws ServletException { this.setConfig(getConfig()); } @Override public void doFilter(ServletRequest sreq, ServletResponse sresp, FilterChain fc) throws IOException, ServletException { int purged; String user; String password; Set<String> groups; HttpServletRequest hreq = (HttpServletRequest) sreq; HttpServletResponse hresp = (HttpServletResponse) sresp; Enumeration<String> forcedRolesHeaders; String accept = hreq.getHeader("Accept"); AcceptTypes ats = AcceptTypes.getPrefferedAcceptTypes(accept); String acceptType = ats.findSuitableMediaType(JSON, XML); HttpHeadersTools httpTools = new HttpHeadersTools(hreq, hresp); LOG.info(String.format("Requesting URL: %s", hreq.getRequestURI())); purged = ldapCache.cleanExpiredByCount(); // Prevent unchecked entries from Living forever if(purged>0){ LOG.info(String.format("cleaning eDir cache: purged %d stale entries", purged)); } String[] splitUrl = hreq.getRequestURL().toString().split(hreq.getContextPath()); if (hreq.getRequestURL().toString().equals(splitUrl[0] + hreq.getContextPath() + "/application.wadl")) { RequestDispatcher dispatcher = hreq.getRequestDispatcher(hreq.getContextPath() + "/?_wadl"); dispatcher.forward(sreq, sresp); return; } if (httpTools.isHeaderTrue("BYPASS-AUTH") && mossoAuth.getConfig().isAllowBypassAuth()) { user = "BYPASS-AUTH"; groups = new HashSet<String>(); LOG.info("Bypassed AUTH.... "); forcedRolesHeaders = hreq.getHeaders("FORCEROLES"); if (mossoAuth.getConfig().isAllowforcedRole() && forcedRolesHeaders != null) { while (forcedRolesHeaders.hasMoreElements()) { String role = forcedRolesHeaders.nextElement(); Map<String, HashSet<String>> roleMap = mossoAuth.getConfig().getRoles(); if (roleMap.containsKey(role) && roleMap.get(role).iterator().hasNext()) { String groupToForce = roleMap.get(role).iterator().next(); groups.add(groupToForce); } } } HeadersRequestWrapper nreq = new HeadersRequestWrapper(hreq); nreq.overideHeader(LDAPGROUPS); nreq.overideHeader(LDAPUSER); nreq.addHeader(LDAPGROUPS, HttpHeadersTools.set2commastr(groups)); nreq.addHeader(LDAPUSER, user); fc.doFilter(sreq, sresp); return; } if (acceptType == null) { acceptType = JSON; } if (overideAcceptType(acceptType) != null) { acceptType = overideAcceptType(acceptType); } if (!httpTools.isBasicAuth()) { hresp.setHeader("WWW-Authenticate", "BASIC realm=\"management\""); sendResponse(hresp, acceptType, requiresAuth, SC_UNAUTHORIZED); return; } if (!httpTools.isValidAuth()) { hresp.setHeader("WWW-Authenticate", "BASIC realm=\"management\""); sendResponse(hresp, acceptType, invalidAuth, SC_UNAUTHORIZED); return; } user = httpTools.getBasicUser(); password = httpTools.getBasicPassword(); CacheEntry<UserEntry> ce = ldapCache.getEntry(user); UserEntry ue; if (ce == null || ce.isExpired()) { ldapCache.remove(user);// Won't hurt if the user was not in the cache // If the entry expired or was not found Bind to eDir. LOG.info("bind eDir"); if (!mossoAuth.testAuth(user, password)) { sendResponse(hresp, acceptType, unAuthorized, SC_UNAUTHORIZED); return; } try { groups = mossoAuth.getGroups(user, password); } catch (NamingException ex) { LOG.error(ex); throw new ServletException("UNABLE to fetch posixgroups from LDAP", ex); } ue = new UserEntry(); ue.setName(user); ue.setPasswd(password); ue.setGroups(groups); groups = new HashSet<String>(ue.getGroups()); ldapCache.put(user, ue); LOG.info(String.format("insert %s into LdapCache", user)); } else { ue = ce.getVal(); if (!password.equals(ue.getPasswd())) { sendResponse(hresp, acceptType, unAuthorized, SC_UNAUTHORIZED); } groups = new HashSet<String>(ue.getGroups()); LOG.info(String.format("Cache hit %s expires in %d secs", user, ce.expiresIn())); } forcedRolesHeaders = hreq.getHeaders("FORCEROLES"); if (mossoAuth.getConfig().isAllowforcedRole() && forcedRolesHeaders != null) { while (forcedRolesHeaders.hasMoreElements()) { String role = forcedRolesHeaders.nextElement(); Map<String, HashSet<String>> roleMap = mossoAuth.getConfig().getRoles(); if (roleMap.containsKey(role) && roleMap.get(role).iterator().hasNext()) { String groupToForce = roleMap.get(role).iterator().next(); groups.add(groupToForce); } } } HeadersRequestWrapper nreq = new HeadersRequestWrapper(hreq); nreq.overideHeader(LDAPGROUPS); nreq.overideHeader(LDAPUSER); nreq.addHeader(LDAPGROUPS, HttpHeadersTools.set2commastr(groups)); nreq.addHeader(LDAPUSER, user); nop(); try { fc.doFilter(nreq, sresp); } catch (Exception ex) { String msg = getExtendedStackTrace(ex); LOG.error(msg, ex); nop(); } return; } public void startConfig() { // Spring should have already initialized the cache ldapCache.setTtl(mossoAuth.getConfig().getTtl()); } private String pojo2xml(Object pojo) throws JAXBException { String result; StringWriter sw = new StringWriter(); Marshaller m = this.xmlJsonConfig.getfCtx().createMarshaller(); m.setSchema(this.xmlJsonConfig.getfSchema()); m.marshal(pojo, sw); result = sw.toString(); return result; } private String pojo2json(Object pojo) throws IOException { return this.xmlJsonConfig.getMapper().writeValueAsString(pojo); } private void sendResponse(HttpServletResponse hresp, String acceptType, Object pojo, int status) throws IOException, ServletException { String content = ""; String contentType; PrintWriter pw; pw = hresp.getWriter(); contentType = String.format("%s; charset=UTF-8", acceptType); if (acceptType.equals(XML)) { try { content = pojo2xml(pojo); } catch (JAXBException ex) { throw new ServletException(ex); } } else if (acceptType.equals(JSON)) { content = pojo2json(pojo); } hresp.setStatus(status); hresp.setContentType(contentType); hresp.setContentLength(content.length()); pw.write(content); pw.flush(); return; } private String overideAcceptType(String uri) { String out = null; Matcher m; m = xmlUriPattern.matcher(uri); if (m.find()) { return XML; } m = jsonUriPattern.matcher(uri); if (m.find()) { return JSON; } return out; } @Override public void destroy() { } private void nop() { } public void setMossoAuth(MossoAuth mossoAuth) { this.mossoAuth = mossoAuth; } public FilterConfig getConfig() { return config; } public void setConfig(FilterConfig config) { this.config = config; } public void setXmlJsonConfig(XmlJsonConfig xmlJsonConfig) { this.xmlJsonConfig = xmlJsonConfig; } public void setLdapCache(SimpleCache<UserEntry> ldapCache) { this.ldapCache = ldapCache; } }