View Javadoc
1   /*
2    * Copyright 2013–2026 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 net.sf.michaelo.tomcat.StringUtils;
23  
24  import org.apache.catalina.authenticator.AuthenticatorBase;
25  import org.apache.catalina.connector.Request;
26  import org.apache.juli.logging.Log;
27  import org.apache.juli.logging.LogFactory;
28  import org.apache.tomcat.util.res.StringManager;
29  import org.ietf.jgss.GSSException;
30  import org.ietf.jgss.Oid;
31  
32  /**
33   * Base implementation for GSS-based authenticators which holds common configuration. See setter methods of
34   * configuration options for this authenticator.
35   */
36  abstract class GSSAuthenticatorBase extends AuthenticatorBase {
37  
38  	protected final Log logger = LogFactory.getLog(getClass());
39  	protected final StringManager sm = StringManager.getManager(getClass());
40  
41  	protected static final Oid KRB5_MECHANISM;
42  	protected static final Oid SPNEGO_MECHANISM;
43  
44  	static {
45  		try {
46  			KRB5_MECHANISM = new Oid("1.2.840.113554.1.2.2");
47  		} catch (GSSException e) {
48  			throw new IllegalStateException("Failed to create OID for Kerberos 5 mechanism");
49  		}
50  
51  		try {
52  			SPNEGO_MECHANISM = new Oid("1.3.6.1.5.5.2");
53  		} catch (GSSException e) {
54  			throw new IllegalStateException("Failed to create OID for SPNEGO mechanism");
55  		}
56  	}
57  
58  	private String loginEntryName;
59  	private boolean omitErrorMessages;
60  	private boolean errorMessagesAsHeaders;
61  	private boolean storeDelegatedCredential;
62  
63  	/**
64  	 * Sets the login entry name which establishes the security context.
65  	 *
66  	 * @param loginEntryName the login entry name
67  	 */
68  	public void setLoginEntryName(String loginEntryName) {
69  		this.loginEntryName = loginEntryName;
70  	}
71  
72  	/**
73  	 * Returns the configured login entry name.
74  	 *
75  	 * @return the login entry name
76  	 */
77  	public String getLoginEntryName() {
78  		return loginEntryName;
79  	}
80  
81  	/**
82  	 * Indicates whether error messages are responded to the client.
83  	 *
84  	 * @return indicator for error message omission
85  	 */
86  	public boolean isOmitErrorMessages() {
87  		return omitErrorMessages;
88  	}
89  
90  	/**
91  	 * Sets whether error messages are responded to the client.
92  	 *
93  	 * @param omitErrorMessages indicator to error omit messages
94  	 */
95  	public void setOmitErrorMessages(boolean omitErrorMessages) {
96  		this.omitErrorMessages = omitErrorMessages;
97  	}
98  
99  	/**
100 	 * Indicates whether error messages will be responded as headers.
101 	 *
102 	 * @return indicates whether error messages will be responded as headers
103 	 */
104 	public boolean isErrorMessagesAsHeaders() {
105 		return errorMessagesAsHeaders;
106 	}
107 
108 	/**
109 	 * Sets whether error messages will be returned as headers.
110 	 *
111 	 * <p>It is not always desired or necessary to produce an error page, e.g., non-interactive clients do not analyze
112 	 * it anyway, but have to consume the response (wasted time and resources). When a client issues a request, the
113 	 * server will write the error messages to either one header: {@code Auth-Error} or {@code Server-Error}.
114 	 *
115 	 * <p>Technically speaking, {@link HttpServletResponse#setStatus(int)} will be called instead of
116 	 * {@link HttpServletResponse#sendError(int, String)}.
117 	 *
118 	 * @param errorMessagesAsHeaders indicates whether error messages will be responded as headers
119 	 */
120 	public void setErrorMessagesAsHeaders(boolean errorMessagesAsHeaders) {
121 		this.errorMessagesAsHeaders = errorMessagesAsHeaders;
122 	}
123 
124 	/**
125 	 * Indicates whether client's (initiator's) delegated credential is stored in the user principal.
126 	 *
127 	 * @return indicates whether client's (initiator's) delegated credential is stored in the user principal.
128 	 */
129 	public boolean isStoreDelegatedCredential() {
130 		return storeDelegatedCredential;
131 	}
132 
133 	/**
134 	 * Sets whether client's (initiator's) delegated credential is stored in the user principal.
135 	 *
136 	 * @param storeDelegatedCredential the store delegated credential indication
137 	 */
138 	public void setStoreDelegatedCredential(boolean storeDelegatedCredential) {
139 		this.storeDelegatedCredential = storeDelegatedCredential;
140 	}
141 
142 	protected void respondErrorMessage(
143 			Request request, HttpServletResponse response, int statusCode, String messageKey, Object... params)
144 			throws IOException {
145 
146 		String message = null;
147 		if (!omitErrorMessages && StringUtils.isNotEmpty(messageKey)) message = sm.getString(messageKey, params);
148 
149 		if (errorMessagesAsHeaders) {
150 			if (StringUtils.isNotEmpty(message)) {
151 				String headerName;
152 				switch (statusCode) {
153 					case HttpServletResponse.SC_UNAUTHORIZED:
154 						headerName = "Auth-Error";
155 						break;
156 					case HttpServletResponse.SC_INTERNAL_SERVER_ERROR:
157 						headerName = "Server-Error";
158 						break;
159 					default:
160 						throw new IllegalArgumentException(String.format("Status code %s not supported", statusCode));
161 				}
162 
163 				response.setHeader(headerName, message);
164 			}
165 
166 			response.setStatus(statusCode);
167 		} else response.sendError(statusCode, message);
168 	}
169 
170 	protected void sendInternalServerError(
171 			Request request, HttpServletResponse response, String messageKey, Object... params) throws IOException {
172 		respondErrorMessage(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, messageKey, params);
173 	}
174 
175 	protected void sendUnauthorized(Request request, HttpServletResponse response, String scheme) throws IOException {
176 		sendUnauthorized(request, response, scheme, null);
177 	}
178 
179 	protected void sendUnauthorized(
180 			Request request, HttpServletResponse response, String scheme, String messageKey, Object... params)
181 			throws IOException {
182 		response.addHeader(AUTH_HEADER_NAME, scheme);
183 
184 		respondErrorMessage(request, response, HttpServletResponse.SC_UNAUTHORIZED, messageKey, params);
185 	}
186 }