001    /**
002     *  Licensed to the Apache Software Foundation (ASF) under one or more
003     *  contributor license agreements.  See the NOTICE file distributed with
004     *  this work for additional information regarding copyright ownership.
005     *  The ASF licenses this file to You under the Apache License, Version 2.0
006     *  (the "License"); you may not use this file except in compliance with
007     *  the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     *  Unless required by applicable law or agreed to in writing, software
012     *  distributed under the License is distributed on an "AS IS" BASIS,
013     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     *  See the License for the specific language governing permissions and
015     *  limitations under the License.
016     */
017    package org.apache.geronimo.jetty6.handler;
018    
019    import java.io.IOException;
020    import java.security.AccessControlContext;
021    import java.security.AccessControlException;
022    import java.security.Principal;
023    
024    import javax.security.auth.Subject;
025    import javax.security.jacc.PolicyContext;
026    import javax.security.jacc.WebResourcePermission;
027    import javax.security.jacc.WebUserDataPermission;
028    import javax.servlet.ServletException;
029    import javax.servlet.http.HttpServletRequest;
030    import javax.servlet.http.HttpServletResponse;
031    
032    import org.apache.geronimo.common.GeronimoSecurityException;
033    import org.apache.geronimo.jetty6.JAASJettyPrincipal;
034    import org.apache.geronimo.jetty6.JAASJettyRealm;
035    import org.apache.geronimo.jetty6.JettyContainer;
036    import org.apache.geronimo.security.Callers;
037    import org.apache.geronimo.security.ContextManager;
038    import org.mortbay.jetty.HttpException;
039    import org.mortbay.jetty.Request;
040    import org.mortbay.jetty.Response;
041    import org.mortbay.jetty.security.Authenticator;
042    import org.mortbay.jetty.security.FormAuthenticator;
043    import org.mortbay.jetty.security.SecurityHandler;
044    
045    public class JettySecurityHandler extends SecurityHandler {
046    
047        private String policyContextID;
048    
049        private JAASJettyPrincipal defaultPrincipal;
050    
051        private String formLoginPath;
052    
053        private JAASJettyRealm realm;
054    
055        public JettySecurityHandler(Authenticator authenticator,
056                JAASJettyRealm userRealm,
057                String policyContextID,
058                Subject defaultSubject) {
059            setAuthenticator(authenticator);
060            this.policyContextID = policyContextID;
061    
062            if (authenticator instanceof FormAuthenticator) {
063                String formLoginPath = ((FormAuthenticator) authenticator).getLoginPage();
064                if (formLoginPath.indexOf('?') > 0) {
065                    formLoginPath = formLoginPath.substring(0, formLoginPath.indexOf('?'));
066                }
067                this.formLoginPath = formLoginPath;
068            } else {
069                formLoginPath = null;
070            }
071    
072            /**
073             * Register our default principal with the ContextManager
074             */
075            if (defaultSubject == null) {
076                defaultSubject = ContextManager.EMPTY;
077            }
078            this.defaultPrincipal = generateDefaultPrincipal(defaultSubject);
079    
080            setUserRealm(userRealm);
081            this.realm = userRealm;
082            assert realm != null;
083        }
084    
085        public boolean hasConstraints() {
086            return true;
087        }
088    
089        public void doStop(JettyContainer jettyContainer) throws Exception {
090            try {
091                super.doStop();
092            }
093            finally {
094                jettyContainer.removeRealm(realm.getSecurityRealmName());
095            }
096        }
097    
098        /* ------------------------------------------------------------ */
099        /*
100         * @see org.mortbay.jetty.security.SecurityHandler#handle(java.lang.String,
101         *      javax.servlet.http.HttpServletRequest,
102         *      javax.servlet.http.HttpServletResponse, int)
103         */
104        public void handle(String target, HttpServletRequest request,
105                HttpServletResponse response, int dispatch) throws IOException,
106                ServletException {
107            String old_policy_id = PolicyContext.getContextID();
108            Callers oldCallers = ContextManager.getCallers();
109    
110            try {
111                PolicyContext.setContextID(policyContextID);
112                PolicyContext.setHandlerData(request);
113    
114                super.handle(target, request, response, dispatch);
115            } finally {
116                PolicyContext.setContextID(old_policy_id);
117                ContextManager.popCallers(oldCallers);
118            }
119        }
120    
121    //    public static Subject getCurrentRoleDesignate(String role) {
122    //        return ((JettySecurityHandler) (WebAppContext.getCurrentWebAppContext()
123    //                .getSecurityHandler())).getRoleDesignate(role);
124    //    }
125    //
126    //    private Subject getRoleDesignate(String roleName) {
127    //        return (Subject) roleDesignates.get(roleName);
128    //    }
129    
130        /**
131         * Check the security constraints using JACC.
132         *
133         * @param pathInContext path in context
134         * @param request       HTTP request
135         * @param response      HTTP response
136         * @return true if the path in context passes the security check, false if
137         *         it fails or a redirection has occured during authentication.
138         */
139        public boolean checkSecurityConstraints(String pathInContext, Request request, Response response) throws IOException {
140            if (formLoginPath != null) {
141                String pathToBeTested = (pathInContext.indexOf('?') > 0 ? pathInContext
142                        .substring(0, pathInContext.indexOf('?'))
143                        : pathInContext);
144    
145                if (pathToBeTested.equals(formLoginPath)) {
146                    return true;
147                }
148            }
149    
150            try {
151                String transportType;
152                if (request.isSecure()) {
153                    transportType = "CONFIDENTIAL";
154                } else if (request.getConnection().isIntegral(request)) {
155                    transportType = "INTEGRAL";
156                } else {
157                    transportType = "NONE";
158                }
159                String substitutedPathInContext = pathInContext;
160                if (substitutedPathInContext.indexOf("%3A") > -1)
161                    substitutedPathInContext = substitutedPathInContext.replaceAll("%3A", "%3A%3A");
162                if (substitutedPathInContext.indexOf(":") > -1)
163                    substitutedPathInContext = substitutedPathInContext.replaceAll(":", "%3A");
164    
165    
166                Authenticator authenticator = getAuthenticator();
167                boolean isAuthenticated = false;
168    
169                if (authenticator instanceof FormAuthenticator
170                        && pathInContext.endsWith(FormAuthenticator.__J_SECURITY_CHECK)) {
171                    /**
172                     * This is a post request to __J_SECURITY_CHECK. Stop now after authentication.
173                     * Whether or not authentication succeeded, we return.
174                     */
175                    authenticator.authenticate(realm, pathInContext, request, response);
176                    return false;
177                }
178                // attempt to access an unprotected resource that is not the
179                // j_security_check.
180                // if we are logged in, return the logged in principal.
181                if (request != null) {
182                    // null response appears to prevent redirect to login page
183                    Principal user = authenticator.authenticate(realm, pathInContext,
184                            request, null);
185                    if (user == null || user == SecurityHandler.__NOBODY) {
186                        //TODO use run-as as nextCaller if present
187                        ContextManager.setCallers(defaultPrincipal.getSubject(), defaultPrincipal.getSubject());
188                        request.setUserPrincipal(new NotChecked());
189                    } else if (user != null) {
190                        isAuthenticated = true;
191                    }
192                }
193    
194    
195                AccessControlContext acc = ContextManager.getCurrentContext();
196    
197                /**
198                 * JACC v1.0 section 4.1.1
199                 */
200                WebUserDataPermission wudp = new WebUserDataPermission(substitutedPathInContext, new String[]{request.getMethod()}, transportType);
201                acc.checkPermission(wudp);
202    
203                WebResourcePermission webResourcePermission = new WebResourcePermission(request);
204                /**
205                 * JACC v1.0 section 4.1.2
206                 */
207                if (isAuthenticated) {
208                    //current user is logged in, this is the actual check
209                    acc.checkPermission(webResourcePermission);
210                } else {
211                    //user is not logged in: if access denied, try to log them in.
212                    try {
213                        acc.checkPermission(webResourcePermission);
214                    } catch (AccessControlException e) {
215                        //not logged in: try to log them in.
216                        Principal user = authenticator.authenticate(realm, pathInContext, request, response);
217                        if (user == SecurityHandler.__NOBODY) {
218                            return true;
219                        }
220                        if (user == null) {
221                            throw e;
222                        }
223                    }
224                }
225    
226            } catch (HttpException he) {
227                response.sendError(he.getStatus(), he.getReason());
228                return false;
229            } catch (AccessControlException ace) {
230                if (!response.isCommitted()) {
231                    response.sendError(403);
232                }
233                return false;
234            }
235            return true;
236        }
237    
238        /**
239         * Generate the default principal from the security config.
240         *
241         * @param defaultSubject The default subject.
242         * @return the default principal
243         * @throws org.apache.geronimo.common.GeronimoSecurityException
244         *          if the default principal cannot be constructed
245         */
246        protected JAASJettyPrincipal generateDefaultPrincipal(Subject defaultSubject)
247                throws GeronimoSecurityException {
248    
249            if (defaultSubject == null) {
250                throw new GeronimoSecurityException(
251                        "Unable to generate default principal");
252            }
253    
254            JAASJettyPrincipal result = new JAASJettyPrincipal("default");
255    
256            result.setSubject(defaultSubject);
257    
258            return result;
259        }
260    
261    }