Skip to content

Commit

Permalink
add access control support for admin service
Browse files Browse the repository at this point in the history
  • Loading branch information
nobodyiam committed Aug 16, 2020
1 parent 27aa832 commit 1e61c38
Show file tree
Hide file tree
Showing 21 changed files with 977 additions and 74 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.ctrip.framework.apollo.adminservice;

import com.ctrip.framework.apollo.adminservice.filter.AdminServiceAuthenticationFilter;
import com.ctrip.framework.apollo.biz.config.BizConfig;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AdminServiceAutoConfiguration {

private final BizConfig bizConfig;

public AdminServiceAutoConfiguration(final BizConfig bizConfig) {
this.bizConfig = bizConfig;
}

@Bean
public FilterRegistrationBean<AdminServiceAuthenticationFilter> adminServiceAuthenticationFilter() {
FilterRegistrationBean<AdminServiceAuthenticationFilter> filterRegistrationBean = new FilterRegistrationBean<>();

filterRegistrationBean.setFilter(new AdminServiceAuthenticationFilter(bizConfig));
filterRegistrationBean.addUrlPatterns("/apps/*");
filterRegistrationBean.addUrlPatterns("/appnamespaces/*");
filterRegistrationBean.addUrlPatterns("/instances/*");
filterRegistrationBean.addUrlPatterns("/items/*");
filterRegistrationBean.addUrlPatterns("/namespaces/*");
filterRegistrationBean.addUrlPatterns("/releases/*");

return filterRegistrationBean;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.ctrip.framework.apollo.adminservice.filter;

import com.ctrip.framework.apollo.biz.config.BizConfig;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import java.io.IOException;
import java.util.List;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;

public class AdminServiceAuthenticationFilter implements Filter {

private static final Logger logger = LoggerFactory
.getLogger(AdminServiceAuthenticationFilter.class);
private static final Splitter ACCESS_TOKEN_SPLITTER = Splitter.on(",").omitEmptyStrings()
.trimResults();

private final BizConfig bizConfig;
private volatile String lastAccessTokens;
private volatile List<String> accessTokenList;

public AdminServiceAuthenticationFilter(BizConfig bizConfig) {
this.bizConfig = bizConfig;
}

@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
if (bizConfig.isAdminServiceAccessControlEnabled()) {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;

String token = request.getHeader(HttpHeaders.AUTHORIZATION);

if (!checkAccessToken(token)) {
logger.warn("Invalid access token: {} for uri: {}", token, request.getRequestURI());
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
return;
}
}

chain.doFilter(req, resp);
}

private boolean checkAccessToken(String token) {
String accessTokens = bizConfig.getAdminServiceAccessTokens();

// if user forget to configure access tokens, then default to pass
if (Strings.isNullOrEmpty(accessTokens)) {
return true;
}

// no need to check
if (Strings.isNullOrEmpty(token)) {
return false;
}

// update cache
if (!accessTokens.equals(lastAccessTokens)) {
synchronized (this) {
accessTokenList = ACCESS_TOKEN_SPLITTER.splitToList(accessTokens);
lastAccessTokens = accessTokens;
}
}

return accessTokenList.contains(token);
}

@Override
public void destroy() {

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ private void postConstruct() {
}

@Value("${local.server.port}")
int port;
protected int port;

protected String url(String path) {
return "http://localhost:" + port + path;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
package com.ctrip.framework.apollo.adminservice.filter;

import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import com.ctrip.framework.apollo.biz.config.BizConfig;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.http.HttpHeaders;

@RunWith(MockitoJUnitRunner.class)
public class AdminServiceAuthenticationFilterTest {

@Mock
private BizConfig bizConfig;
private HttpServletRequest servletRequest;
private HttpServletResponse servletResponse;
private FilterChain filterChain;

private AdminServiceAuthenticationFilter authenticationFilter;

@Before
public void setUp() throws Exception {
authenticationFilter = new AdminServiceAuthenticationFilter(bizConfig);
initVariables();
}

private void initVariables() {
servletRequest = mock(HttpServletRequest.class);
servletResponse = mock(HttpServletResponse.class);
filterChain = mock(FilterChain.class);
}

@Test
public void testWithAccessControlDisabled() throws Exception {
when(bizConfig.isAdminServiceAccessControlEnabled()).thenReturn(false);

authenticationFilter.doFilter(servletRequest, servletResponse, filterChain);

verify(bizConfig, times(1)).isAdminServiceAccessControlEnabled();
verify(filterChain, times(1)).doFilter(servletRequest, servletResponse);
verify(bizConfig, never()).getAdminServiceAccessTokens();
verify(servletRequest, never()).getHeader(HttpHeaders.AUTHORIZATION);
verify(servletResponse, never()).sendError(anyInt(), anyString());
}

@Test
public void testWithAccessControlEnabledWithTokenSpecifiedWithValidTokenPassed()
throws Exception {
String someValidToken = "someToken";

when(bizConfig.isAdminServiceAccessControlEnabled()).thenReturn(true);
when(bizConfig.getAdminServiceAccessTokens()).thenReturn(someValidToken);
when(servletRequest.getHeader(HttpHeaders.AUTHORIZATION)).thenReturn(someValidToken);

authenticationFilter.doFilter(servletRequest, servletResponse, filterChain);

verify(bizConfig, times(1)).isAdminServiceAccessControlEnabled();
verify(bizConfig, times(1)).getAdminServiceAccessTokens();
verify(filterChain, times(1)).doFilter(servletRequest, servletResponse);
verify(servletResponse, never()).sendError(anyInt(), anyString());
}

@Test
public void testWithAccessControlEnabledWithTokenSpecifiedWithInvalidTokenPassed()
throws Exception {
String someValidToken = "someValidToken";
String someInvalidToken = "someInvalidToken";

when(bizConfig.isAdminServiceAccessControlEnabled()).thenReturn(true);
when(bizConfig.getAdminServiceAccessTokens()).thenReturn(someValidToken);
when(servletRequest.getHeader(HttpHeaders.AUTHORIZATION)).thenReturn(someInvalidToken);

authenticationFilter.doFilter(servletRequest, servletResponse, filterChain);

verify(bizConfig, times(1)).isAdminServiceAccessControlEnabled();
verify(bizConfig, times(1)).getAdminServiceAccessTokens();
verify(servletResponse, times(1))
.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
verify(filterChain, never()).doFilter(servletRequest, servletResponse);
}

@Test
public void testWithAccessControlEnabledWithTokenSpecifiedWithNoTokenPassed() throws Exception {
String someValidToken = "someValidToken";

when(bizConfig.isAdminServiceAccessControlEnabled()).thenReturn(true);
when(bizConfig.getAdminServiceAccessTokens()).thenReturn(someValidToken);
when(servletRequest.getHeader(HttpHeaders.AUTHORIZATION)).thenReturn(null);

authenticationFilter.doFilter(servletRequest, servletResponse, filterChain);

verify(bizConfig, times(1)).isAdminServiceAccessControlEnabled();
verify(bizConfig, times(1)).getAdminServiceAccessTokens();
verify(servletResponse, times(1))
.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
verify(filterChain, never()).doFilter(servletRequest, servletResponse);
}


@Test
public void testWithAccessControlEnabledWithMultipleTokenSpecifiedWithValidTokenPassed()
throws Exception {
String someToken = "someToken";
String anotherToken = "anotherToken";

when(bizConfig.isAdminServiceAccessControlEnabled()).thenReturn(true);
when(bizConfig.getAdminServiceAccessTokens())
.thenReturn(String.format("%s,%s", someToken, anotherToken));
when(servletRequest.getHeader(HttpHeaders.AUTHORIZATION)).thenReturn(someToken);

authenticationFilter.doFilter(servletRequest, servletResponse, filterChain);

verify(bizConfig, times(1)).isAdminServiceAccessControlEnabled();
verify(bizConfig, times(1)).getAdminServiceAccessTokens();
verify(filterChain, times(1)).doFilter(servletRequest, servletResponse);
verify(servletResponse, never()).sendError(anyInt(), anyString());
}

@Test
public void testWithAccessControlEnabledWithNoTokenSpecifiedWithTokenPassed() throws Exception {
String someToken = "someToken";

when(bizConfig.isAdminServiceAccessControlEnabled()).thenReturn(true);
when(bizConfig.getAdminServiceAccessTokens()).thenReturn(null);
when(servletRequest.getHeader(HttpHeaders.AUTHORIZATION)).thenReturn(someToken);

authenticationFilter.doFilter(servletRequest, servletResponse, filterChain);

verify(bizConfig, times(1)).isAdminServiceAccessControlEnabled();
verify(bizConfig, times(1)).getAdminServiceAccessTokens();
verify(filterChain, times(1)).doFilter(servletRequest, servletResponse);
verify(servletResponse, never()).sendError(anyInt(), anyString());
}

@Test
public void testWithAccessControlEnabledWithNoTokenSpecifiedWithNoTokenPassed() throws Exception {
String someToken = "someToken";

when(bizConfig.isAdminServiceAccessControlEnabled()).thenReturn(true);
when(bizConfig.getAdminServiceAccessTokens()).thenReturn(null);
when(servletRequest.getHeader(HttpHeaders.AUTHORIZATION)).thenReturn(null);

authenticationFilter.doFilter(servletRequest, servletResponse, filterChain);

verify(bizConfig, times(1)).isAdminServiceAccessControlEnabled();
verify(bizConfig, times(1)).getAdminServiceAccessTokens();
verify(filterChain, times(1)).doFilter(servletRequest, servletResponse);
verify(servletResponse, never()).sendError(anyInt(), anyString());
}

@Test
public void testWithConfigChanged() throws Exception {
String someToken = "someToken";
String anotherToken = "anotherToken";
String yetAnotherToken = "yetAnotherToken";

// case 1: init state
when(bizConfig.isAdminServiceAccessControlEnabled()).thenReturn(true);
when(bizConfig.getAdminServiceAccessTokens()).thenReturn(someToken);

when(servletRequest.getHeader(HttpHeaders.AUTHORIZATION)).thenReturn(someToken);

authenticationFilter.doFilter(servletRequest, servletResponse, filterChain);

verify(filterChain, times(1)).doFilter(servletRequest, servletResponse);
verify(servletResponse, never()).sendError(anyInt(), anyString());

// case 2: change access tokens specified
initVariables();
when(bizConfig.getAdminServiceAccessTokens())
.thenReturn(String.format("%s,%s", anotherToken, yetAnotherToken));
when(servletRequest.getHeader(HttpHeaders.AUTHORIZATION)).thenReturn(someToken);

authenticationFilter.doFilter(servletRequest, servletResponse, filterChain);

verify(servletResponse, times(1))
.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
verify(filterChain, never()).doFilter(servletRequest, servletResponse);

initVariables();
when(servletRequest.getHeader(HttpHeaders.AUTHORIZATION)).thenReturn(anotherToken);

authenticationFilter.doFilter(servletRequest, servletResponse, filterChain);

verify(filterChain, times(1)).doFilter(servletRequest, servletResponse);
verify(servletResponse, never()).sendError(anyInt(), anyString());

// case 3: change access control flag
initVariables();
when(bizConfig.isAdminServiceAccessControlEnabled()).thenReturn(false);

authenticationFilter.doFilter(servletRequest, servletResponse, filterChain);

verify(filterChain, times(1)).doFilter(servletRequest, servletResponse);
verify(servletResponse, never()).sendError(anyInt(), anyString());
verify(servletRequest, never()).getHeader(HttpHeaders.AUTHORIZATION);
}
}
Loading

0 comments on commit 1e61c38

Please sign in to comment.