View Javadoc
1   /*
2    * Copyright 2013–2023 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 jakarta.servlet.http.HttpServletResponse;
19  
20  import java.io.IOException;
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.
33   * See setter methods of configuration options for this authenticator.
34   */
35  abstract class GSSAuthenticatorBase extends AuthenticatorBase {
36  
37  	protected final Log logger = LogFactory.getLog(getClass());
38  	protected final StringManager sm = StringManager.getManager(getClass());
39  
40  	protected final static Oid KRB5_MECHANISM;
41  	protected final static Oid SPNEGO_MECHANISM;
42  
43  	static {
44  		try {
45  			KRB5_MECHANISM = new Oid("1.2.840.113554.1.2.2");
46  		} catch (GSSException e) {
47  			throw new IllegalStateException("Failed to create OID for Kerberos 5 mechanism");
48  		}
49  
50  		try {
51  			SPNEGO_MECHANISM = new Oid("1.3.6.1.5.5.2");
52  		} catch (GSSException e) {
53  			throw new IllegalStateException("Failed to create OID for SPNEGO mechanism");
54  		}
55  	}
56  
57  	private String loginEntryName;
58  	private boolean omitErrorMessages;
59  	private boolean errorMessagesAsHeaders;
60  	private boolean storeDelegatedCredential;
61  
62  	/**
63  	 * Sets the login entry name which establishes the security context.
64  	 *
65  	 * @param loginEntryName
66  	 *            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
94  	 *            indicator to error omit messages
95  	 */
96  	public void setOmitErrorMessages(boolean omitErrorMessages) {
97  		this.omitErrorMessages = omitErrorMessages;
98  	}
99  
100 	/**
101 	 * Indicates whether error messages will be responded as headers.
102 	 *
103 	 * @return indicates whether error messages will be responded as headers
104 	 */
105 	public boolean isErrorMessagesAsHeaders() {
106 		return errorMessagesAsHeaders;
107 	}
108 
109 	/**
110 	 * Sets whether error messages will be returned as headers.
111 	 *
112 	 * <p>
113 	 * It is not always desired or necessary to produce an error page, e.g., non-interactive clients do
114 	 * not analyze it anyway, but have to consume the response (wasted time and resources). When a
115 	 * client issues a request, the server will write the error messages to either one header:
116 	 * {@code Auth-Error} or {@code Server-Error}.
117 	 * <p>
118 	 * Technically speaking, {@link HttpServletResponse#setStatus(int)} will be called instead of
119 	 * {@link HttpServletResponse#sendError(int, String)}.
120 	 *
121 	 * @param errorMessagesAsHeaders
122 	 *            indicates whether error messages will be responded as headers
123 	 */
124 	public void setErrorMessagesAsHeaders(boolean errorMessagesAsHeaders) {
125 		this.errorMessagesAsHeaders = errorMessagesAsHeaders;
126 	}
127 
128 	/**
129 	 * Indicates whether client's (initiator's) delegated credential is stored in the user
130 	 * principal.
131 	 *
132 	 * @return indicates whether client's (initiator's) delegated credential is stored in the user
133 	 *         principal.
134 	 */
135 	public boolean isStoreDelegatedCredential() {
136 		return storeDelegatedCredential;
137 	}
138 
139 	/**
140 	 * Sets whether client's (initiator's) delegated credential is stored in the user principal.
141 	 *
142 	 * @param storeDelegatedCredential
143 	 *            the store delegated credential indication
144 	 */
145 	public void setStoreDelegatedCredential(boolean storeDelegatedCredential) {
146 		this.storeDelegatedCredential = storeDelegatedCredential;
147 	}
148 
149 	protected void respondErrorMessage(Request request, HttpServletResponse response,
150 			int statusCode, String messageKey, Object... params) throws IOException {
151 
152 		String message = null;
153 		if (!omitErrorMessages && StringUtils.isNotEmpty(messageKey))
154 			message = sm.getString(messageKey, params);
155 
156 		if (errorMessagesAsHeaders) {
157 			if (StringUtils.isNotEmpty(message)) {
158 				String headerName;
159 				switch (statusCode) {
160 				case HttpServletResponse.SC_UNAUTHORIZED:
161 					headerName = "Auth-Error";
162 					break;
163 				case HttpServletResponse.SC_INTERNAL_SERVER_ERROR:
164 					headerName = "Server-Error";
165 					break;
166 				default:
167 					throw new IllegalArgumentException(
168 							String.format("Status code %s not supported", statusCode));
169 				}
170 
171 				response.setHeader(headerName, message);
172 			}
173 
174 			response.setStatus(statusCode);
175 		} else
176 			response.sendError(statusCode, message);
177 
178 	}
179 
180 	protected void sendInternalServerError(Request request, HttpServletResponse response,
181 			String messageKey, Object... params) throws IOException {
182 		respondErrorMessage(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
183 				messageKey, params);
184 	}
185 
186 	protected void sendUnauthorized(Request request, HttpServletResponse response, String scheme)
187 			throws IOException {
188 		sendUnauthorized(request, response, scheme, null);
189 	}
190 
191 	protected void sendUnauthorized(Request request, HttpServletResponse response, String scheme,
192 			String messageKey, Object... params) throws IOException {
193 		response.addHeader(AUTH_HEADER_NAME, scheme);
194 
195 		respondErrorMessage(request, response, HttpServletResponse.SC_UNAUTHORIZED, messageKey,
196 				params);
197 	}
198 
199 }