package org.cloudfoundry.identity.uaa.zone;
import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication;
import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails;
import org.cloudfoundry.identity.uaa.oauth.UaaOauth2Authentication;
import org.cloudfoundry.identity.uaa.util.UaaStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.cloudfoundry.identity.uaa.zone.ZoneManagementScopes.ZONES_ZONE_ID_PREFIX;
import static org.cloudfoundry.identity.uaa.zone.ZoneManagementScopes.getZoneSwitchingScopes;
/**
* If the X-Identity-Zone-Id header is set and the user has a scope
* of zones.<id>.admin, this filter switches the IdentityZone in the IdentityZoneHolder
* to the one in the header.
*
*/
public class IdentityZoneSwitchingFilter extends OncePerRequestFilter {
@Autowired
public IdentityZoneSwitchingFilter(IdentityZoneProvisioning dao) {
super();
this.dao = dao;
}
private final IdentityZoneProvisioning dao;
public static final String HEADER = "X-Identity-Zone-Id";
public static final String SUBDOMAIN_HEADER = "X-Identity-Zone-Subdomain";
public static final List<String> zoneScopestoNotStripPrefix = Collections.unmodifiableList(
Arrays.asList(
"admin",
"read")
);
protected OAuth2Authentication getAuthenticationForZone(String identityZoneId, HttpServletRequest servletRequest) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if(!(authentication instanceof OAuth2Authentication)) {
return null;
}
OAuth2Authentication oa = (OAuth2Authentication) authentication;
Object oaDetails = oa.getDetails();
//strip client scopes
OAuth2Request request = oa.getOAuth2Request();
Collection<String> requestAuthorities = UaaStringUtils.getStringsFromAuthorities(request.getAuthorities());
Set<String> clientScopes = new HashSet<>();
Set<String> clientAuthorities = new HashSet<>();
for (String s : getZoneSwitchingScopes(identityZoneId)) {
String scope = stripPrefix(s, identityZoneId);
if (request.getScope().contains(s)) {
clientScopes.add(scope);
}
if (requestAuthorities.contains(s)) {
clientAuthorities.add(scope);
}
}
request = new OAuth2Request(
request.getRequestParameters(),
request.getClientId(),
UaaStringUtils.getAuthoritiesFromStrings(clientAuthorities),
request.isApproved(),
clientScopes,
request.getResourceIds(),
request.getRedirectUri(),
request.getResponseTypes(),
request.getExtensions()
);
UaaAuthentication userAuthentication = (UaaAuthentication)oa.getUserAuthentication();
if (userAuthentication!=null) {
userAuthentication = new UaaAuthentication(
userAuthentication.getPrincipal(),
null,
UaaStringUtils.getAuthoritiesFromStrings(clientScopes),
new UaaAuthenticationDetails(servletRequest),
true, userAuthentication.getAuthenticatedTime());
}
oa = new UaaOauth2Authentication(((UaaOauth2Authentication)oa).getTokenValue(), IdentityZoneHolder.get().getId(), request, userAuthentication);
oa.setDetails(oaDetails);
return oa;
}
protected String stripPrefix(String s, String identityZoneId) {
if (!StringUtils.hasText(s)) {
return s;
}
//dont touch the zones.{zone.id}.admin scope
String replace = ZONES_ZONE_ID_PREFIX+identityZoneId+".";
for (String scope : zoneScopestoNotStripPrefix) {
if (s.equals(replace + scope)) {
return s;
}
}
//replace zones.<id>.
if (s.startsWith(replace)) {
return s.substring(replace.length());
}
return s;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String identityZoneIdFromHeader = request.getHeader(HEADER);
String identityZoneSubDomainFromHeader = request.getHeader(SUBDOMAIN_HEADER);
if (StringUtils.isEmpty(identityZoneIdFromHeader) && StringUtils.isEmpty(identityZoneSubDomainFromHeader)) {
filterChain.doFilter(request, response);
return;
}
IdentityZone identityZone = validateIdentityZone(identityZoneIdFromHeader, identityZoneSubDomainFromHeader);
if (identityZone == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "Identity zone with id/subdomain " + identityZoneIdFromHeader + "/" + identityZoneSubDomainFromHeader + " does not exist");
return;
}
String identityZoneId = identityZone.getId();
OAuth2Authentication oAuth2Authentication = getAuthenticationForZone(identityZoneId, request);
if (IdentityZoneHolder.isUaa() && oAuth2Authentication != null && !oAuth2Authentication.getOAuth2Request().getScope().isEmpty()) {
SecurityContextHolder.getContext().setAuthentication(oAuth2Authentication);
} else {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not authorized to switch to IdentityZone with id "+identityZoneId);
return;
}
IdentityZone originalIdentityZone = IdentityZoneHolder.get();
try {
IdentityZoneHolder.set(identityZone);
filterChain.doFilter(request, response);
} finally {
IdentityZoneHolder.set(originalIdentityZone);
}
}
private IdentityZone validateIdentityZone(String identityZoneId, String identityZoneSubDomain) throws IOException {
IdentityZone identityZone = null;
try {
if (StringUtils.isEmpty(identityZoneId)) {
identityZone = dao.retrieveBySubdomain(identityZoneSubDomain);
} else {
identityZone = dao.retrieve(identityZoneId);
}
} catch (ZoneDoesNotExistsException | EmptyResultDataAccessException ex) {
} catch (Exception ex) {
throw ex;
}
return identityZone;
}
}