Skip to content

Commit bb83bf6

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

File tree

8 files changed

+230
-19
lines changed

8 files changed

+230
-19
lines changed

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

Lines changed: 20 additions & 11 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,16 +171,17 @@ 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-
}
180+
// if (!ssoEnabled) {
181+
// filterChain.doFilter(servletRequest, servletResponse);
182+
//
183+
// return;
184+
// }
176185

177186
HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
178187

@@ -187,11 +196,11 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo
187196
return;
188197
}
189198

190-
if (jwtProperties == null || isAuthenticated()) {
191-
filterChain.doFilter(servletRequest, servletResponse);
192-
193-
return;
194-
}
199+
// if (jwtProperties == null || isAuthenticated()) {
200+
// filterChain.doFilter(servletRequest, servletResponse);
201+
//
202+
// return;
203+
// }
195204

196205
if (LOG.isDebugEnabled()) {
197206
LOG.debug("Knox ssoEnabled {} {}", ssoEnabled, httpRequest.getRequestURI());
@@ -308,7 +317,7 @@ protected String getJWTFromCookie(HttpServletRequest req) {
308317
for (Cookie cookie : cookies) {
309318
if (cookieName.equals(cookie.getName())) {
310319
LOG.debug("{} cookie has been found and is being processed", cookieName);
311-
320+
setSsoEnabled(true);
312321
serializedJWT = cookie.getValue();
313322
break;
314323
}

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+
private 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.info("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: 6 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,10 @@ protected void configure(HttpSecurity httpSecurity) throws Exception {
220222
.passwordParameter("j_password")
221223
.and()
222224
.logout()
223-
.logoutSuccessUrl("/login.jsp")
225+
// .logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET"))
226+
.logoutSuccessHandler(customLogoutSuccessHandler)
224227
.deleteCookies("ATLASSESSIONID")
225-
.logoutUrl("/logout.html");
226-
228+
.logoutUrl("/logout");
227229
//@formatter:on
228230

229231
boolean configMigrationEnabled = !StringUtils.isEmpty(configuration.getString(ATLAS_MIGRATION_MODE_FILENAME));
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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.slf4j.Logger;
24+
import org.slf4j.LoggerFactory;
25+
import org.springframework.security.core.Authentication;
26+
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
27+
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
28+
import org.springframework.stereotype.Component;
29+
30+
import javax.inject.Inject;
31+
import javax.servlet.http.HttpServletRequest;
32+
import javax.servlet.http.HttpServletResponse;
33+
34+
import java.io.IOException;
35+
import java.util.HashMap;
36+
import java.util.Map;
37+
38+
@Component
39+
public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler implements LogoutSuccessHandler {
40+
private final ObjectMapper mapper;
41+
private static final Logger LOG = LoggerFactory.getLogger(CustomLogoutSuccessHandler.class);
42+
43+
@Inject
44+
public CustomLogoutSuccessHandler(ObjectMapper mapper) {
45+
this.mapper = mapper;
46+
}
47+
48+
@Override
49+
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
50+
request.getServletContext().removeAttribute(request.getRequestedSessionId());
51+
response.setContentType("application/json;charset=UTF-8");
52+
response.setHeader("Cache-Control", "no-cache");
53+
response.setHeader("X-Frame-Options", "DENY");
54+
55+
try {
56+
Map<String, Object> responseMap = new HashMap<>();
57+
responseMap.put("statusCode", HttpServletResponse.SC_OK);
58+
responseMap.put("msgDesc", "Logout Successful");
59+
String jsonStr = mapper.writeValueAsString(responseMap);
60+
61+
response.setStatus(HttpServletResponse.SC_OK);
62+
response.getWriter().write(jsonStr);
63+
LOG.info("Log-out Successfully done. Returning Json : " + jsonStr);
64+
} catch (IOException e) {
65+
LOG.info("Error while writing JSON in HttpServletResponse");
66+
}
67+
}
68+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
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.mockito.InjectMocks;
21+
import org.mockito.Mock;
22+
import org.mockito.Mockito;
23+
import org.mockito.MockitoAnnotations;
24+
import org.springframework.mock.web.MockHttpServletRequest;
25+
import org.springframework.mock.web.MockHttpServletResponse;
26+
import org.springframework.mock.web.MockHttpSession;
27+
import org.testng.annotations.BeforeMethod;
28+
import org.testng.annotations.Test;
29+
30+
import javax.servlet.FilterChain;
31+
import javax.servlet.ServletException;
32+
import javax.servlet.http.Cookie;
33+
34+
import java.io.IOException;
35+
import java.util.Arrays;
36+
37+
import static org.testng.Assert.assertFalse;
38+
import static org.testng.Assert.assertNotNull;
39+
import static org.testng.Assert.assertNull;
40+
import static org.testng.Assert.assertTrue;
41+
42+
public class AtlasKnoxSSOAuthenticationFilterTest {
43+
@Mock
44+
private FilterChain filterChain;
45+
46+
@InjectMocks
47+
private AtlasKnoxSSOAuthenticationFilter filter;
48+
49+
private MockHttpServletRequest request;
50+
private MockHttpServletResponse response;
51+
private MockHttpSession session;
52+
53+
@BeforeMethod
54+
public void testSetup() {
55+
MockitoAnnotations.openMocks(this); // Required for @Mock
56+
57+
request = new MockHttpServletRequest();
58+
response = new MockHttpServletResponse();
59+
session = new MockHttpSession();
60+
request.setSession(session);
61+
62+
request.setRequestURI("/api/atlas/admin/checksso");
63+
request.addHeader("User-Agent", "Chrome");
64+
}
65+
66+
@Test
67+
public void testDoFilter_withoutJwt_setsSsoDisabled() throws IOException, ServletException {
68+
request.setCookies(); // clear cookies for this test
69+
filter.doFilter(request, response, filterChain);
70+
71+
assertFalse(filter.isSsoEnabled()); // because no JWT
72+
assertNull(filter.getJWTFromCookie(request));
73+
74+
Mockito.verify(filterChain).doFilter(request, response);
75+
}
76+
77+
@Test
78+
public void testDoFilter_withJwt_setsSsoEnabledTrue() throws IOException, ServletException {
79+
request.setCookies(new Cookie("hadoop-jwt", "dummy-jwt-token"));
80+
filter.doFilter(request, response, filterChain);
81+
82+
assertTrue(filter.isSsoEnabled());
83+
assertNotNull(filter.getJWTFromCookie(request));
84+
assertNotNull(request.getCookies());
85+
assertTrue(Arrays.stream(request.getCookies()).anyMatch(cookie -> cookie.getValue().equals("dummy-jwt-token")));
86+
87+
Mockito.verify(filterChain).doFilter(request, response);
88+
}
89+
}

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)