/** * Licensed to The Apereo Foundation under one or more contributor license * agreements. See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * * The Apereo Foundation licenses this file to you under the Educational * Community 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://opensource.org/licenses/ecl2.txt * * 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.opencastproject.kernel.security; import static org.opencastproject.security.api.SecurityConstants.GLOBAL_ADMIN_ROLE; import static org.opencastproject.security.api.SecurityConstants.GLOBAL_SUDO_ROLE; import static org.opencastproject.security.api.SecurityConstants.GLOBAL_SYSTEM_ROLES; import static org.opencastproject.security.api.SecurityConstants.ORGANIZATION_HEADER; import static org.opencastproject.security.api.SecurityConstants.ROLES_HEADER; import static org.opencastproject.security.api.SecurityConstants.RUN_AS_USER_HEADER; import static org.opencastproject.security.api.SecurityConstants.RUN_WITH_ROLES; import static org.opencastproject.security.api.SecurityConstants.USER_HEADER; import org.opencastproject.security.api.JaxbOrganization; import org.opencastproject.security.api.JaxbRole; import org.opencastproject.security.api.JaxbUser; import org.opencastproject.security.api.Organization; import org.opencastproject.security.api.OrganizationDirectoryService; import org.opencastproject.security.api.SecurityConstants; import org.opencastproject.security.api.SecurityService; import org.opencastproject.security.api.User; import org.opencastproject.security.api.UserDirectoryService; import org.opencastproject.security.util.SecurityUtil; import org.opencastproject.util.NotFoundException; import com.entwinemedia.fn.Fn2; import com.entwinemedia.fn.Stream; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.Arrays; import java.util.Collection; 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; /** * Security filter used to set the organization and user in remote implementations. */ public class RemoteUserAndOrganizationFilter implements Filter { /** The logger */ private static final Logger logger = LoggerFactory.getLogger(OrganizationFilter.class); /** The security service */ protected SecurityService securityService = null; /** The organization directory to use when resolving organizations */ protected OrganizationDirectoryService organizationDirectory = null; /** The user directory used to load users */ protected UserDirectoryService userDirectory = null; /** * @see javax.servlet.Filter#init(javax.servlet.FilterConfig) */ @Override public void init(FilterConfig filterConfig) throws ServletException { } /** * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, * javax.servlet.FilterChain) */ @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; // Keep the original organization and user final Organization originalOrganization = securityService.getOrganization(); final User originalUser = securityService.getUser(); // Organization and user as specified by the request Organization requestedOrganization = originalOrganization; User requestedUser = originalUser; try { // See if there is an organization provided in the request String organizationHeader = httpRequest.getHeader(ORGANIZATION_HEADER); if (StringUtils.isNotBlank(organizationHeader)) { // Organization switching is only allowed if the request is coming in with the global admin role enabled if (!originalUser.hasRole(GLOBAL_ADMIN_ROLE)) { logger.warn("An unauthorized request is trying to switch from organization '{}' to '{}'", originalOrganization.getId(), organizationHeader); ((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN); return; } try { requestedOrganization = organizationDirectory.getOrganization(organizationHeader); securityService.setOrganization(requestedOrganization); logger.trace("Switching to organization '{}' from request header {}", requestedOrganization.getId(), ORGANIZATION_HEADER); } catch (NotFoundException e) { logger.warn("Non-existing organization '{}' specified in request header {}", organizationHeader, ORGANIZATION_HEADER); ((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN); return; } } else { logger.trace("Request organization remains '{}'", originalOrganization.getId()); } // See if there is a user provided in the request String userHeader = httpRequest.getHeader(USER_HEADER); if (StringUtils.isBlank(userHeader)) { userHeader = httpRequest.getHeader(RUN_AS_USER_HEADER); } if (StringUtils.isNotBlank(userHeader)) { // User switching is only allowed if the request is coming in with the global sudo role enabled if (!originalUser.hasRole(GLOBAL_SUDO_ROLE)) { logger.warn("An unauthorized request is trying to switch from user '{}' to '{}'", originalUser.getUsername(), userHeader); ((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN); return; } if (SecurityConstants.GLOBAL_ANONYMOUS_USERNAME.equals(userHeader)) { requestedUser = SecurityUtil.createAnonymousUser(requestedOrganization); logger.trace("Request user is switched to '{}'", requestedUser.getUsername()); } else { requestedUser = userDirectory.loadUser(userHeader); // Does the target user exist? if (requestedUser == null) { logger.warn("Unable to switch to non-existing user '{}' as specified in request header {}", userHeader, USER_HEADER); ((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN); return; } if (!originalUser.hasRole(GLOBAL_ADMIN_ROLE)) { // if the original user did not have system privileges, the target user must not gain those, either. for (String systemRole : GLOBAL_SYSTEM_ROLES) { if (requestedUser.hasRole(systemRole)) { logger.warn("An unauthorized request is trying to switch to an admin user, from '{}' to '{}'", originalUser.getUsername(), userHeader); ((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN); return; } } // make sure the user does not gain organization administrator privileges String organizationAdminRole = requestedOrganization.getAdminRole(); if (requestedUser.hasRole(organizationAdminRole)) { logger.warn("An unauthorized request is trying to switch to an admin user, from '{}' to '{}'", originalUser.getUsername(), userHeader); ((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN); return; } } } logger.trace("Switching from user '{}' to user '{}' from request header '{}'", new Object[] { originalUser.getUsername(), requestedUser.getUsername(), USER_HEADER }); securityService.setUser(requestedUser); } // See if there are roles provided in the request String rolesHeader = httpRequest.getHeader(ROLES_HEADER); if (StringUtils.isBlank(rolesHeader)) { rolesHeader = httpRequest.getHeader(RUN_WITH_ROLES); } if (StringUtils.isNotBlank(rolesHeader)) { // Role switching is only allowed if the request is coming in with the global sudo role enabled if (!originalUser.hasRole(GLOBAL_SUDO_ROLE)) { logger.warn("An unauthorized request is trying to switch roles from '{}' to '{}'", requestedUser.getRoles(), rolesHeader); ((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN); return; } Collection<String> requestedRoles = Arrays.asList(StringUtils.split(rolesHeader, ",")); if (!originalUser.hasRole(GLOBAL_ADMIN_ROLE)) { // Role switching is only allowed to non-system roles for (String systemRole : GLOBAL_SYSTEM_ROLES) { if (requestedRoles.contains(systemRole)) { logger.warn("An unauthorized request by user '{}' is trying to gain admin role '{}'", originalUser.getUsername(), systemRole); ((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN); return; } } // Role switching is only allowed to non-organization administrator roles String organizationAdminRole = requestedOrganization.getAdminRole(); if (requestedRoles.contains(organizationAdminRole)) { logger.warn("An unauthorized request by user '{}' is trying to gain admin role '{}'", originalUser.getUsername(), organizationAdminRole); ((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN); return; } } // If no user has been provider by the request create a virtual user if (StringUtils.isBlank(userHeader)) { requestedUser = SecurityUtil.createAnonymousUser(requestedOrganization); } // Set roles to requested user requestedUser = new JaxbUser(requestedUser.getUsername(), requestedUser.getPassword(), requestedUser.getName(), requestedUser.getEmail(), requestedUser.getProvider(), requestedUser.canLogin(), JaxbOrganization.fromOrganization(requestedUser.getOrganization()), Stream.$(requestedRoles) .map(toJaxbRole._2(requestedOrganization)).toSet()); logger.trace("Request roles '{}' are amended to user '{}'", rolesHeader, requestedUser.getUsername()); securityService.setUser(requestedUser); } // Execute the rest of the filter chain logger.trace("Executing the filter chain with user '{}@{}'", requestedUser.getUsername(), requestedOrganization.getId()); chain.doFilter(httpRequest, response); } finally { securityService.setOrganization(originalOrganization); securityService.setUser(originalUser); } } /** * @see javax.servlet.Filter#destroy() */ @Override public void destroy() { } /** * Sets the security service. * * @param securityService * the securityService to set */ void setSecurityService(SecurityService securityService) { this.securityService = securityService; } /** * Sets a reference to the organization directory service. * * @param organizationDirectory * the organization directory */ void setOrganizationDirectoryService(OrganizationDirectoryService organizationDirectory) { this.organizationDirectory = organizationDirectory; } /** * Sets a reference to the user directory service. * * @param userDirectory * the user directory */ void setUserDirectoryService(UserDirectoryService userDirectory) { this.userDirectory = userDirectory; } private static final Fn2<String, Organization, JaxbRole> toJaxbRole = new Fn2<String, Organization, JaxbRole>() { @Override public JaxbRole apply(String role, Organization organization) { return new JaxbRole(role, JaxbOrganization.fromOrganization(organization)); } }; }