1 /*
2  * JBoss, Home of Professional Open Source.
3  * Copyright 2014 Red Hat, Inc., and individual contributors
4  * as indicated by the @author tags.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * 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, software
13  *  distributed under the License is distributed on an "AS IS" BASIS,
14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *  See the License for the specific language governing permissions and
16  *  limitations under the License.
17  */

18 package io.undertow.servlet.handlers.security;
19
20 import io.undertow.security.api.AuthenticatedSessionManager;
21 import io.undertow.security.api.AuthenticatedSessionManager.AuthenticatedSession;
22 import io.undertow.security.api.NotificationReceiver;
23 import io.undertow.security.api.SecurityContext;
24 import io.undertow.security.api.SecurityNotification;
25 import io.undertow.security.api.SecurityNotification.EventType;
26 import io.undertow.server.HttpHandler;
27 import io.undertow.server.HttpServerExchange;
28 import io.undertow.server.session.Session;
29 import io.undertow.servlet.handlers.ServletRequestContext;
30 import io.undertow.servlet.spec.HttpSessionImpl;
31 import io.undertow.servlet.spec.ServletContextImpl;
32 import io.undertow.servlet.util.SavedRequest;
33
34 import java.security.AccessController;
35
36 import javax.servlet.http.HttpSession;
37
38 /**
39  * {@link HttpHandler} responsible for setting up the {@link AuthenticatedSessionManager} for cached authentications and
40  * registering a {@link NotificationReceiver} to receive the security notifications.
41  *
42  * This handler also forces the session to change its session ID on sucessful authentication.
43  *
44  * @author <a href="mailto:darran.lofthouse@jboss.com">Darran Lofthouse</a>
45  */

46 public class CachedAuthenticatedSessionHandler implements HttpHandler {
47
48     public static final String ATTRIBUTE_NAME = CachedAuthenticatedSessionHandler.class.getName() + ".AuthenticatedSession";
49
50     public static final String NO_ID_CHANGE_REQUIRED = CachedAuthenticatedSessionHandler.class.getName() + ".NoIdChangeRequired";
51
52     private final NotificationReceiver NOTIFICATION_RECEIVER = new SecurityNotificationReceiver();
53     private final AuthenticatedSessionManager SESSION_MANAGER = new ServletAuthenticatedSessionManager();
54
55     private final HttpHandler next;
56     private final ServletContextImpl servletContext;
57
58     public CachedAuthenticatedSessionHandler(final HttpHandler next, final ServletContextImpl servletContext) {
59         this.next = next;
60         this.servletContext = servletContext;
61     }
62
63
64     @Override
65     public void handleRequest(HttpServerExchange exchange) throws Exception {
66         SecurityContext securityContext = exchange.getSecurityContext();
67         securityContext.registerNotificationReceiver(NOTIFICATION_RECEIVER);
68
69         HttpSession session = servletContext.getSession(exchange, false);
70         // If there was no existing HttpSession then there could not be a cached AuthenticatedSession so don't bother setting
71         // the AuthenticatedSessionManager.
72         if (session != null) {
73             exchange.putAttachment(AuthenticatedSessionManager.ATTACHMENT_KEY, SESSION_MANAGER);
74             SavedRequest.tryRestoreRequest(exchange, session); //not sure if this is where it belongs
75         }
76
77         next.handleRequest(exchange);
78     }
79
80     private class SecurityNotificationReceiver implements NotificationReceiver {
81
82         @Override
83         public void handleNotification(SecurityNotification notification) {
84             EventType eventType = notification.getEventType();
85             HttpSessionImpl httpSession = servletContext.getSession(notification.getExchange(), false);
86             switch (eventType) {
87                 case AUTHENTICATED:
88                     if (isCacheable(notification)) {
89                         if(servletContext.getDeployment().getDeploymentInfo().isChangeSessionIdOnLogin()) {
90                             if (httpSession != null) {
91                                 Session session = underlyingSession(httpSession);
92                                 if (!httpSession.isNew() &&
93                                         !httpSession.isInvalid() &&
94                                         session.getAttribute(NO_ID_CHANGE_REQUIRED) == null) {
95                                     ServletRequestContext src = notification.getExchange().getAttachment(ServletRequestContext.ATTACHMENT_KEY);
96                                     src.getOriginalRequest().changeSessionId();
97                                 }
98                                 if(session.getAttribute(NO_ID_CHANGE_REQUIRED) == null) {
99                                     session.setAttribute(NO_ID_CHANGE_REQUIRED, true);
100                                 }
101                             }
102                         }
103                         if(httpSession == null) {
104                             httpSession = servletContext.getSession(notification.getExchange(), true);
105                         }
106                         Session session = underlyingSession(httpSession);
107
108                         // It is normal for this notification to be received when using a previously cached session - in that
109                         // case the IDM would have been given an opportunity to re-load the Account so updating here ready for
110                         // the next request is desired.
111                         session.setAttribute(ATTRIBUTE_NAME,
112                                 new AuthenticatedSession(notification.getAccount(), notification.getMechanism()));
113                     }
114                     break;
115                 case LOGGED_OUT:
116                     if (httpSession != null) {
117                         Session session = underlyingSession(httpSession);
118                         session.removeAttribute(ATTRIBUTE_NAME);
119                         session.removeAttribute(NO_ID_CHANGE_REQUIRED);
120                     }
121                     break;
122             }
123         }
124
125     }
126
127     protected Session underlyingSession(HttpSessionImpl httpSession) {
128         Session session;
129         if (System.getSecurityManager() == null) {
130             session = httpSession.getSession();
131         } else {
132             session = AccessController.doPrivileged(new HttpSessionImpl.UnwrapSessionAction(httpSession));
133         }
134         return session;
135     }
136
137     private class ServletAuthenticatedSessionManager implements AuthenticatedSessionManager {
138
139         @Override
140         public AuthenticatedSession lookupSession(HttpServerExchange exchange) {
141             HttpSessionImpl httpSession = servletContext.getSession(exchange, false);
142             if (httpSession != null) {
143                 Session session = underlyingSession(httpSession);
144                 return (AuthenticatedSession) session.getAttribute(ATTRIBUTE_NAME);
145             }
146             return null;
147         }
148
149         @Override
150         public void clearSession(HttpServerExchange exchange) {
151             HttpSessionImpl httpSession = servletContext.getSession(exchange, false);
152             if (httpSession != null) {
153                 Session session = underlyingSession(httpSession);
154                 session.removeAttribute(ATTRIBUTE_NAME);
155             }
156         }
157
158     }
159
160     private boolean isCacheable(final SecurityNotification notification) {
161         return notification.isProgramatic() || notification.isCachingRequired();
162     }
163
164 }
165