View Javadoc
1   /*
2    * Copyright 2013–2019 Michael Osipov
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package net.sf.michaelo.tomcat.authenticator;
17  
18  import java.io.IOException;
19  
20  import javax.servlet.http.HttpServletResponse;
21  
22  import org.apache.catalina.authenticator.AuthenticatorBase;
23  import org.apache.catalina.connector.Request;
24  import org.apache.commons.lang3.StringUtils;
25  import org.apache.juli.logging.Log;
26  import org.apache.juli.logging.LogFactory;
27  import org.apache.tomcat.util.res.StringManager;
28  import org.ietf.jgss.GSSException;
29  import org.ietf.jgss.Oid;
30  
31  /**
32   * Base implementation for GSS-based authenticators which holds common configuration information.
33   * See setter methods of configuration options for this authenticator.
34   *
35   * @version $Id: GSSAuthenticatorBase.java 317 2019-03-09 21:26:28Z michael-o $
36   */
37  abstract class GSSAuthenticatorBase extends AuthenticatorBase {
38  
39  	protected final Log logger = LogFactory.getLog(getClass());
40  	protected final StringManager sm = StringManager.getManager(getClass());
41  
42  	protected final static Oid KRB5_MECHANISM;
43  	protected final static Oid SPNEGO_MECHANISM;
44  
45  	static {
46  		try {
47  			KRB5_MECHANISM = new Oid("1.2.840.113554.1.2.2");
48  		} catch (GSSException e) {
49  			throw new IllegalStateException("failed to create OID for Kerberos 5 mechanism");
50  		}
51  
52  		try {
53  			SPNEGO_MECHANISM = new Oid("1.3.6.1.5.5.2");
54  		} catch (GSSException e) {
55  			throw new IllegalStateException("failed to create OID for SPNEGO mechanism");
56  		}
57  	}
58  
59  	private String loginEntryName;
60  	private boolean omitErrorMessages;
61  	private boolean errorMessagesAsHeaders;
62  	private boolean storeDelegatedCredential;
63  
64  	/**
65  	 * Sets the login entry name which establishes the security context.
66  	 *
67  	 * @param loginEntryName
68  	 *            the login entry name
69  	 */
70  	public void setLoginEntryName(String loginEntryName) {
71  		this.loginEntryName = loginEntryName;
72  	}
73  
74  	/**
75  	 * Returns the configured login entry name.
76  	 *
77  	 * @return the login entry name
78  	 */
79  	public String getLoginEntryName() {
80  		return loginEntryName;
81  	}
82  
83  	/**
84  	 * Indicates whether error messages are responded to the client.
85  	 *
86  	 * @return indicator for error message omission
87  	 */
88  	public boolean isOmitErrorMessages() {
89  		return omitErrorMessages;
90  	}
91  
92  	/**
93  	 * Sets whether error messages are responded to the client.
94  	 *
95  	 * @param omitErrorMessages
96  	 *            indicator to error omit messages
97  	 */
98  	public void setOmitErrorMessages(boolean omitErrorMessages) {
99  		this.omitErrorMessages = omitErrorMessages;
100 	}
101 
102 	/**
103 	 * Indicates whether error messages will be responded as headers.
104 	 *
105 	 * @return indicates whether error messages will be responded as headers
106 	 */
107 	public boolean isErrorMessagesAsHeaders() {
108 		return errorMessagesAsHeaders;
109 	}
110 
111 	/**
112 	 * Sets whether error messages will be returned as headers.
113 	 *
114 	 * <p>
115 	 * It is not always desired or necessary to produce an error page, e.g., non-human clients do
116 	 * not analyze it anyway but have to consume the response (wasted time and resources). When a
117 	 * client issues a request, the server will write the error messages to either one header:
118 	 * {@code Auth-Error} or {@code Server-Error}.
119 	 * <p>
120 	 * Technically speaking, {@link HttpServletResponse#setStatus(int)} will be called instead of
121 	 * {@link HttpServletResponse#sendError(int, String)}.
122 	 *
123 	 * @param errorMessagesAsHeaders
124 	 *            indicates whether error messages will be responded as headers
125 	 */
126 	public void setErrorMessagesAsHeaders(boolean errorMessagesAsHeaders) {
127 		this.errorMessagesAsHeaders = errorMessagesAsHeaders;
128 	}
129 
130 	/**
131 	 * Indicates whether client's (initiator's) delegated credential is stored in the user
132 	 * principal.
133 	 *
134 	 * @return indicates whether client's (initiator's) delegated credential is stored in the user
135 	 *         principal.
136 	 */
137 	public boolean isStoreDelegatedCredential() {
138 		return storeDelegatedCredential;
139 	}
140 
141 	/**
142 	 * Sets whether client's (initiator's) delegated credential is stored in the user principal.
143 	 *
144 	 * @param storeDelegatedCredential
145 	 *            the store delegated credential indication
146 	 */
147 	public void setStoreDelegatedCredential(boolean storeDelegatedCredential) {
148 		this.storeDelegatedCredential = storeDelegatedCredential;
149 	}
150 
151 	protected void respondErrorMessage(Request request, HttpServletResponse response,
152 			int statusCode, String messageKey, Object... params) throws IOException {
153 
154 		String message = null;
155 		if (!omitErrorMessages && StringUtils.isNotEmpty(messageKey))
156 			message = sm.getString(messageKey, params);
157 
158 		if (errorMessagesAsHeaders) {
159 			if (StringUtils.isNotEmpty(message)) {
160 				String headerName;
161 				switch (statusCode) {
162 				case HttpServletResponse.SC_UNAUTHORIZED:
163 					headerName = "Auth-Error";
164 					break;
165 				case HttpServletResponse.SC_INTERNAL_SERVER_ERROR:
166 					headerName = "Server-Error";
167 					break;
168 				default:
169 					throw new IllegalArgumentException(
170 							String.format("status code %s not supported", statusCode));
171 				}
172 
173 				response.setHeader(headerName, message);
174 			}
175 
176 			response.setStatus(statusCode);
177 		} else
178 			response.sendError(statusCode, message);
179 
180 	}
181 
182 	protected void sendInternalServerError(Request request, HttpServletResponse response,
183 			String messageKey, Object... params) throws IOException {
184 		respondErrorMessage(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
185 				messageKey, params);
186 	}
187 
188 	protected void sendUnauthorized(Request request, HttpServletResponse response, String scheme)
189 			throws IOException {
190 		sendUnauthorized(request, response, scheme, null);
191 	}
192 
193 	protected void sendUnauthorized(Request request, HttpServletResponse response, String scheme,
194 			String messageKey, Object... params) throws IOException {
195 		response.addHeader(AUTH_HEADER_NAME, scheme);
196 
197 		respondErrorMessage(request, response, HttpServletResponse.SC_UNAUTHORIZED, messageKey,
198 				params);
199 	}
200 
201 }