Skip to content

Commit 960ced0

Browse files
Atlas[Backend] Fix for improving logout mechanism in Atlas Backend code base
1 parent 54befb3 commit 960ced0

File tree

9 files changed

+251
-21
lines changed

9 files changed

+251
-21
lines changed

webapp/src/main/java/org/apache/atlas/web/filters/AtlasKnoxSSOAuthenticationFilter.java

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,14 @@ public class AtlasKnoxSSOAuthenticationFilter implements Filter {
101101
private boolean ssoEnabled;
102102
private JWSVerifier verifier;
103103

104+
public boolean isSsoEnabled() {
105+
return ssoEnabled;
106+
}
107+
108+
public void setSsoEnabled(boolean ssoEnabled) {
109+
this.ssoEnabled = ssoEnabled;
110+
}
111+
104112
@Inject
105113
public AtlasKnoxSSOAuthenticationFilter(AtlasAuthenticationProvider authenticationProvider) {
106114
this.authenticationProvider = authenticationProvider;
@@ -163,17 +171,12 @@ public void init(FilterConfig filterConfig) throws ServletException {
163171
*/
164172
@Override
165173
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
174+
setSsoEnabled(false);
166175
HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;
167176
AtlasResponseRequestWrapper responseWrapper = new AtlasResponseRequestWrapper(httpResponse);
168177

169178
HeadersUtil.setSecurityHeaders(responseWrapper);
170179

171-
if (!ssoEnabled) {
172-
filterChain.doFilter(servletRequest, servletResponse);
173-
174-
return;
175-
}
176-
177180
HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
178181

179182
if (LOG.isDebugEnabled()) {
@@ -187,12 +190,6 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo
187190
return;
188191
}
189192

190-
if (jwtProperties == null || isAuthenticated()) {
191-
filterChain.doFilter(servletRequest, servletResponse);
192-
193-
return;
194-
}
195-
196193
if (LOG.isDebugEnabled()) {
197194
LOG.debug("Knox ssoEnabled {} {}", ssoEnabled, httpRequest.getRequestURI());
198195
}
@@ -308,7 +305,7 @@ protected String getJWTFromCookie(HttpServletRequest req) {
308305
for (Cookie cookie : cookies) {
309306
if (cookieName.equals(cookie.getName())) {
310307
LOG.debug("{} cookie has been found and is being processed", cookieName);
311-
308+
setSsoEnabled(true);
312309
serializedJWT = cookie.getValue();
313310
break;
314311
}

webapp/src/main/java/org/apache/atlas/web/filters/HeadersUtil.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ public class HeadersUtil {
5050
public static final String X_REQUESTED_WITH_VALUE = "XMLHttpRequest";
5151
public static final int SC_AUTHENTICATION_TIMEOUT = 419;
5252
public static final String CONFIG_PREFIX_HTTP_RESPONSE_HEADER = "atlas.headers";
53+
public static final String CACHE_CONTROL = "Cache-Control";
54+
public static final String CACHE_CONTROL_VAL = "no-cache";
5355

5456
private static final Map<String, String> HEADER_MAP = new HashMap<>();
5557

webapp/src/main/java/org/apache/atlas/web/filters/RestUtil.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public class RestUtil {
3030
private static final Logger LOG = LoggerFactory.getLogger(RestUtil.class);
3131

3232
public static final String TIMEOUT_ACTION = "timeout";
33-
public static final String LOGOUT_URL = "/logout.html";
33+
public static final String LOGOUT_URL = "/logout";
3434
public static final String DELIMITTER = "://";
3535

3636
private static final String PROXY_ATLAS_URL_PATH = "/atlas";

webapp/src/main/java/org/apache/atlas/web/resources/AdminResource.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
import org.apache.atlas.utils.AtlasJson;
7777
import org.apache.atlas.utils.AtlasPerfTracer;
7878
import org.apache.atlas.web.filters.AtlasCSRFPreventionFilter;
79+
import org.apache.atlas.web.filters.AtlasKnoxSSOAuthenticationFilter;
7980
import org.apache.atlas.web.model.DebugMetrics;
8081
import org.apache.atlas.web.service.AtlasDebugMetricsSink;
8182
import org.apache.atlas.web.service.ServiceState;
@@ -192,6 +193,7 @@ public class AdminResource {
192193
private final boolean isUiTasksTabEnabled;
193194
private final AtlasAuditReductionService auditReductionService;
194195
private Response version;
196+
public AtlasKnoxSSOAuthenticationFilter atlasKnoxSSOAuthenticationFilter;
195197

196198
@Context
197199
private HttpServletRequest httpServletRequest;
@@ -205,7 +207,7 @@ public AdminResource(ServiceState serviceState, MetricsService metricsService, A
205207
MigrationProgressService migrationProgressService, AtlasServerService serverService,
206208
ExportImportAuditService exportImportAuditService, AtlasEntityStore entityStore,
207209
AtlasPatchManager patchManager, AtlasAuditService auditService, EntityAuditRepository auditRepository,
208-
TaskManagement taskManagement, AtlasDebugMetricsSink debugMetricsRESTSink, AtlasAuditReductionService atlasAuditReductionService, AtlasMetricsUtil atlasMetricsUtil) {
210+
TaskManagement taskManagement, AtlasDebugMetricsSink debugMetricsRESTSink, AtlasAuditReductionService atlasAuditReductionService, AtlasMetricsUtil atlasMetricsUtil, AtlasKnoxSSOAuthenticationFilter atlasKnoxSSOAuthenticationFilter) {
209211
this.serviceState = serviceState;
210212
this.metricsService = metricsService;
211213
this.exportService = exportService;
@@ -224,6 +226,7 @@ public AdminResource(ServiceState serviceState, MetricsService metricsService, A
224226
this.debugMetricsRESTSink = debugMetricsRESTSink;
225227
this.auditReductionService = atlasAuditReductionService;
226228
this.atlasMetricsUtil = atlasMetricsUtil;
229+
this.atlasKnoxSSOAuthenticationFilter = atlasKnoxSSOAuthenticationFilter;
227230

228231
if (atlasProperties != null) {
229232
this.defaultUIVersion = atlasProperties.getString(DEFAULT_UI_VERSION, UI_VERSION_V2);
@@ -1098,6 +1101,14 @@ public Response serviceReadiness() throws AtlasBaseException {
10981101
}
10991102
}
11001103

1104+
@GET
1105+
@Path("/checksso")
1106+
@Produces(MediaType.TEXT_PLAIN)
1107+
public String checkSSO() {
1108+
LOG.debug("SSO Details: {}", atlasKnoxSSOAuthenticationFilter.isSsoEnabled());
1109+
return String.valueOf(atlasKnoxSSOAuthenticationFilter.isSsoEnabled());
1110+
}
1111+
11011112
private void updateCriteriaWithDefaultValues(AuditReductionCriteria auditReductionCriteria) {
11021113
if (auditReductionCriteria.getDefaultAgeoutTTLInDays() <= 0) {
11031114
auditReductionCriteria.setDefaultAgeoutTTLInDays(AtlasConfiguration.ATLAS_AUDIT_DEFAULT_AGEOUT_TTL.getInt());
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.atlas.web.security;
21+
22+
import com.fasterxml.jackson.databind.ObjectMapper;
23+
import org.springframework.context.annotation.Bean;
24+
import org.springframework.context.annotation.Configuration;
25+
26+
@Configuration
27+
public class AtlasSecurityCommonConfig {
28+
@Bean
29+
public ObjectMapper objectMapper() {
30+
return new ObjectMapper();
31+
}
32+
}

webapp/src/main/java/org/apache/atlas/web/security/AtlasSecurityConfig.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ public class AtlasSecurityConfig extends WebSecurityConfigurerAdapter {
103103
private final StaleTransactionCleanupFilter staleTransactionCleanupFilter;
104104
private final ActiveServerFilter activeServerFilter;
105105
private final boolean keycloakEnabled;
106+
private final CustomLogoutSuccessHandler customLogoutSuccessHandler;
106107

107108
@Value("${keycloak.configurationFile:WEB-INF/keycloak.json}")
108109
private Resource keycloakConfigFileResource;
@@ -120,7 +121,7 @@ public AtlasSecurityConfig(AtlasKnoxSSOAuthenticationFilter ssoAuthenticationFil
120121
AtlasAuthenticationEntryPoint atlasAuthenticationEntryPoint,
121122
Configuration configuration,
122123
StaleTransactionCleanupFilter staleTransactionCleanupFilter,
123-
ActiveServerFilter activeServerFilter) {
124+
ActiveServerFilter activeServerFilter, CustomLogoutSuccessHandler customLogoutSuccessHandler) {
124125
this.ssoAuthenticationFilter = ssoAuthenticationFilter;
125126
this.csrfPreventionFilter = atlasCSRFPreventionFilter;
126127
this.atlasAuthenticationFilter = atlasAuthenticationFilter;
@@ -131,6 +132,7 @@ public AtlasSecurityConfig(AtlasKnoxSSOAuthenticationFilter ssoAuthenticationFil
131132
this.configuration = configuration;
132133
this.staleTransactionCleanupFilter = staleTransactionCleanupFilter;
133134
this.activeServerFilter = activeServerFilter;
135+
this.customLogoutSuccessHandler = customLogoutSuccessHandler;
134136

135137
this.keycloakEnabled = configuration.getBoolean(AtlasAuthenticationProvider.KEYCLOAK_AUTH_METHOD, false);
136138
}
@@ -220,10 +222,9 @@ protected void configure(HttpSecurity httpSecurity) throws Exception {
220222
.passwordParameter("j_password")
221223
.and()
222224
.logout()
223-
.logoutSuccessUrl("/login.jsp")
225+
.logoutSuccessHandler(customLogoutSuccessHandler)
224226
.deleteCookies("ATLASSESSIONID")
225-
.logoutUrl("/logout.html");
226-
227+
.logoutUrl("/logout");
227228
//@formatter:on
228229

229230
boolean configMigrationEnabled = !StringUtils.isEmpty(configuration.getString(ATLAS_MIGRATION_MODE_FILENAME));
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.atlas.web.security;
21+
22+
import com.fasterxml.jackson.databind.ObjectMapper;
23+
import org.apache.atlas.web.filters.HeadersUtil;
24+
import org.slf4j.Logger;
25+
import org.slf4j.LoggerFactory;
26+
import org.springframework.security.core.Authentication;
27+
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
28+
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
29+
import org.springframework.stereotype.Component;
30+
31+
import javax.inject.Inject;
32+
import javax.servlet.http.HttpServletRequest;
33+
import javax.servlet.http.HttpServletResponse;
34+
35+
import java.io.IOException;
36+
import java.util.HashMap;
37+
import java.util.Map;
38+
39+
@Component
40+
public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler implements LogoutSuccessHandler {
41+
private final ObjectMapper mapper;
42+
private static final Logger LOG = LoggerFactory.getLogger(CustomLogoutSuccessHandler.class);
43+
44+
@Inject
45+
public CustomLogoutSuccessHandler(ObjectMapper mapper) {
46+
this.mapper = mapper;
47+
}
48+
49+
@Override
50+
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
51+
request.getServletContext().removeAttribute(request.getRequestedSessionId());
52+
response.setContentType("application/json;charset=UTF-8");
53+
response.setHeader(HeadersUtil.CACHE_CONTROL, HeadersUtil.CACHE_CONTROL_VAL);
54+
response.setHeader(HeadersUtil.X_FRAME_OPTIONS_KEY, HeadersUtil.X_FRAME_OPTIONS_VAL);
55+
56+
try {
57+
Map<String, Object> responseMap = new HashMap<>();
58+
responseMap.put("statusCode", HttpServletResponse.SC_OK);
59+
responseMap.put("msgDesc", "Logout Successful");
60+
String jsonStr = mapper.writeValueAsString(responseMap);
61+
62+
response.setStatus(HttpServletResponse.SC_OK);
63+
response.getWriter().write(jsonStr);
64+
65+
LOG.debug("Log-out Successfully done. Returning Json : {}", jsonStr);
66+
} catch (IOException e) {
67+
LOG.debug("Error while writing JSON in HttpServletResponse");
68+
}
69+
}
70+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.atlas.web.filters;
19+
20+
import org.apache.atlas.web.resources.AdminResource;
21+
import org.mockito.InjectMocks;
22+
import org.mockito.Mock;
23+
import org.mockito.Mockito;
24+
import org.mockito.MockitoAnnotations;
25+
import org.springframework.mock.web.MockHttpServletRequest;
26+
import org.springframework.mock.web.MockHttpServletResponse;
27+
import org.springframework.mock.web.MockHttpSession;
28+
import org.testng.annotations.BeforeMethod;
29+
import org.testng.annotations.Test;
30+
31+
import javax.servlet.FilterChain;
32+
import javax.servlet.ServletException;
33+
import javax.servlet.http.Cookie;
34+
35+
import java.io.IOException;
36+
import java.util.Arrays;
37+
38+
import static org.testng.Assert.assertEquals;
39+
import static org.testng.Assert.assertFalse;
40+
import static org.testng.Assert.assertNotNull;
41+
import static org.testng.Assert.assertNull;
42+
import static org.testng.Assert.assertTrue;
43+
44+
public class AtlasKnoxSSOAuthenticationFilterTest {
45+
@Mock
46+
private FilterChain filterChain;
47+
48+
@InjectMocks
49+
private AtlasKnoxSSOAuthenticationFilter filter;
50+
51+
private MockHttpServletRequest request;
52+
private MockHttpServletResponse response;
53+
private MockHttpSession session;
54+
55+
AdminResource adminResource;
56+
57+
@BeforeMethod
58+
public void testSetup() {
59+
MockitoAnnotations.openMocks(this);
60+
request = new MockHttpServletRequest();
61+
response = new MockHttpServletResponse();
62+
session = new MockHttpSession();
63+
request.setSession(session);
64+
65+
request.setRequestURI("/api/atlas/admin/checksso");
66+
request.addHeader("User-Agent", "Chrome");
67+
68+
adminResource = new AdminResource(null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, filter);
69+
}
70+
71+
@Test
72+
public void testDoFilter_withoutJwt_setsSsoDisabled() throws IOException, ServletException {
73+
request.setCookies(); // clear cookies for this test
74+
filter.doFilter(request, response, filterChain);
75+
76+
assertFalse(filter.isSsoEnabled());
77+
assertNull(filter.getJWTFromCookie(request));
78+
79+
Mockito.verify(filterChain).doFilter(request, response);
80+
}
81+
82+
@Test
83+
public void testDoFilter_withJwt_setsSsoEnabledTrue() throws IOException, ServletException {
84+
request.setCookies(new Cookie("hadoop-jwt", "dummy-jwt-token"));
85+
filter.doFilter(request, response, filterChain);
86+
87+
assertTrue(filter.isSsoEnabled());
88+
assertNotNull(filter.getJWTFromCookie(request));
89+
assertNotNull(request.getCookies());
90+
assertTrue(Arrays.stream(request.getCookies())
91+
.anyMatch(cookie -> cookie.getValue().equals("dummy-jwt-token")));
92+
93+
Mockito.verify(filterChain).doFilter(request, response);
94+
}
95+
96+
@Test
97+
public void test_CheckSSO_API_true() throws ServletException, IOException {
98+
request.setCookies(new Cookie("hadoop-jwt", "dummy-jwt-token"));
99+
filter.doFilter(request, response, filterChain);
100+
101+
filter.setSsoEnabled(true);
102+
103+
String result = adminResource.checkSSO();
104+
assertEquals(result, String.valueOf(filter.isSsoEnabled()));
105+
}
106+
107+
@Test
108+
public void test_CheckSSO_API_false() throws ServletException, IOException {
109+
request.setCookies();
110+
filter.doFilter(request, response, filterChain);
111+
112+
filter.setSsoEnabled(false);
113+
114+
String result = adminResource.checkSSO();
115+
assertEquals(result, String.valueOf(filter.isSsoEnabled()));
116+
}
117+
}

webapp/src/test/java/org/apache/atlas/web/resources/AdminResourceTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public void setup() {
4848
public void testStatusOfActiveServerIsReturned() throws IOException {
4949
when(serviceState.getState()).thenReturn(ServiceState.ServiceStateValue.ACTIVE);
5050

51-
AdminResource adminResource = new AdminResource(serviceState, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null);
51+
AdminResource adminResource = new AdminResource(serviceState, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null);
5252
Response response = adminResource.getStatus();
5353

5454
assertEquals(response.getStatus(), HttpServletResponse.SC_OK);
@@ -62,7 +62,7 @@ public void testStatusOfActiveServerIsReturned() throws IOException {
6262
public void testResourceGetsValueFromServiceState() throws IOException {
6363
when(serviceState.getState()).thenReturn(ServiceState.ServiceStateValue.PASSIVE);
6464

65-
AdminResource adminResource = new AdminResource(serviceState, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null);
65+
AdminResource adminResource = new AdminResource(serviceState, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null);
6666
Response response = adminResource.getStatus();
6767

6868
verify(serviceState).getState();

0 commit comments

Comments
 (0)