2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4 * Copyright (c) 2013-2015 Oracle and/or its affiliates. All rights reserved.
6 * The contents of this file are subject to the terms of either the GNU
7 * General Public License Version 2 only ("GPL") or the Common Development
8 * and Distribution License("CDDL") (collectively, the "License"). You
9 * may not use this file except in compliance with the License. You can
10 * obtain a copy of the License at
11 * http://glassfish.java.net/public/CDDL+GPL_1_1.html
12 * or packager/legal/LICENSE.txt. See the License for the specific
13 * language governing permissions and limitations under the License.
15 * When distributing the software, include this License Header Notice in each
16 * file and include the License file at packager/legal/LICENSE.txt.
18 * GPL Classpath Exception:
19 * Oracle designates this particular file as subject to the "Classpath"
20 * exception as provided by Oracle in the GPL Version 2 section of the License
21 * file that accompanied this code.
24 * If applicable, add the following below the License Header, with the fields
25 * enclosed by brackets [] replaced by your own identifying information:
26 * "Portions Copyright [year] [name of copyright owner]"
29 * If you wish your version of this file to be governed by only the CDDL or
30 * only the GPL Version 2, indicate your decision by adding "[Contributor]
31 * elects to include this software in this distribution under the [CDDL or GPL
32 * Version 2] license." If you don't indicate a single choice of license, a
33 * recipient has the option to distribute your version of this file under
34 * either the CDDL, the GPL Version 2 or to extend the choice of license to
35 * its licensees as provided above. However, if you add GPL Version 2 code
36 * and therefore, elected the GPL Version 2 license, then the option applies
37 * only if the new code is made subject to such option by the copyright
40 package org.openhab.binding.lametrictime.internal.api.authentication;
42 import java.io.IOException;
43 import java.io.InputStream;
45 import java.nio.charset.Charset;
46 import java.util.List;
49 import javax.annotation.Priority;
50 import javax.ws.rs.Priorities;
51 import javax.ws.rs.client.Client;
52 import javax.ws.rs.client.ClientBuilder;
53 import javax.ws.rs.client.ClientRequestContext;
54 import javax.ws.rs.client.ClientRequestFilter;
55 import javax.ws.rs.client.ClientResponseContext;
56 import javax.ws.rs.client.ClientResponseFilter;
57 import javax.ws.rs.client.Entity;
58 import javax.ws.rs.client.Invocation;
59 import javax.ws.rs.client.WebTarget;
60 import javax.ws.rs.core.Configuration;
61 import javax.ws.rs.core.HttpHeaders;
62 import javax.ws.rs.core.MediaType;
63 import javax.ws.rs.core.MultivaluedHashMap;
64 import javax.ws.rs.core.MultivaluedMap;
65 import javax.ws.rs.core.Response;
68 * Http Authentication filter that provides basic and digest authentication (based on RFC 2617).
70 * @author Miroslav Fuksa
72 @Priority(Priorities.AUTHENTICATION)
73 class HttpAuthenticationFilter implements ClientRequestFilter, ClientResponseFilter {
76 * Authentication type.
80 * Basic authentication.
85 private static final String REQUEST_PROPERTY_FILTER_REUSED = "org.openhab.binding.lametrictime.api.authentication.HttpAuthenticationFilter.reused";
86 private static final String REQUEST_PROPERTY_OPERATION = "org.openhab.binding.lametrictime.api.authentication.HttpAuthenticationFilter.operation";
89 * Encoding used for authentication calculations.
91 static final Charset CHARACTER_SET = Charset.forName("iso-8859-1");
93 private final HttpAuthenticationFeature.Mode mode;
95 private final BasicAuthenticator basicAuth;
98 * Create a new filter instance.
101 * @param basicCredentials Basic credentials (can be {@code null} if this filter does not work in the
102 * basic mode or if no default credentials are defined).
103 * @param digestCredentials Digest credentials (can be {@code null} if this filter does not work in the
104 * digest mode or if no default credentials are defined).
105 * @param configuration Configuration (non-{@code null}).
107 HttpAuthenticationFilter(HttpAuthenticationFeature.Mode mode, Credentials basicCredentials,
108 Configuration configuration) {
111 case BASIC_PREEMPTIVE:
112 case BASIC_NON_PREEMPTIVE:
113 this.basicAuth = new BasicAuthenticator(basicCredentials);
116 this.basicAuth = new BasicAuthenticator(basicCredentials);
119 throw new IllegalStateException("Not implemented.");
124 public void filter(ClientRequestContext request) throws IOException {
125 if ("true".equals(request.getProperty(REQUEST_PROPERTY_FILTER_REUSED))) {
129 if (request.getHeaders().containsKey(HttpHeaders.AUTHORIZATION)) {
133 Type operation = null;
134 if (mode == HttpAuthenticationFeature.Mode.BASIC_PREEMPTIVE) {
135 basicAuth.filterRequest(request);
136 operation = Type.BASIC;
137 } else if (mode == HttpAuthenticationFeature.Mode.BASIC_NON_PREEMPTIVE) {
141 if (operation != null) {
142 request.setProperty(REQUEST_PROPERTY_OPERATION, operation);
147 public void filter(ClientRequestContext request, ClientResponseContext response) throws IOException {
148 if ("true".equals(request.getProperty(REQUEST_PROPERTY_FILTER_REUSED))) {
152 Type result = null; // which authentication is requested: BASIC or DIGEST
153 boolean authenticate;
155 if (response.getStatus() == Response.Status.UNAUTHORIZED.getStatusCode()) {
156 String authString = response.getHeaders().getFirst(HttpHeaders.WWW_AUTHENTICATE);
157 if (authString != null) {
158 final String upperCaseAuth = authString.trim().toUpperCase();
159 if (upperCaseAuth.startsWith("BASIC")) {
162 // unknown authentication -> this filter cannot authenticate with this method
168 authenticate = false;
171 if (mode == HttpAuthenticationFeature.Mode.BASIC_PREEMPTIVE) {
172 // do nothing -> 401 will be returned to the client
173 } else if (mode == HttpAuthenticationFeature.Mode.BASIC_NON_PREEMPTIVE) {
174 if (authenticate && result == Type.BASIC) {
175 basicAuth.filterResponseAndAuthenticate(request, response);
177 } else if (mode == HttpAuthenticationFeature.Mode.UNIVERSAL) {
179 boolean success = false;
181 // now we have the challenge response and we can authenticate
182 if (result == Type.BASIC) {
183 success = basicAuth.filterResponseAndAuthenticate(request, response);
189 private String getCacheKey(ClientRequestContext request) {
190 return request.getUri().toString() + ":" + request.getMethod();
194 * Repeat the {@code request} with provided {@code newAuthorizationHeader}
195 * and update the {@code response} with newest response data.
197 * @param request Request context.
198 * @param response Response context (will be updated with the new response data).
199 * @param newAuthorizationHeader {@code Authorization} header that should be added to the new request.
200 * @return {@code true} is the authentication was successful ({@code true} if 401 response code was not returned;
201 * {@code false} otherwise).
203 static boolean repeatRequest(ClientRequestContext request, ClientResponseContext response,
204 String newAuthorizationHeader) {
205 Client client = ClientBuilder.newClient(request.getConfiguration());
206 String method = request.getMethod();
207 MediaType mediaType = request.getMediaType();
208 URI lUri = request.getUri();
210 WebTarget resourceTarget = client.target(lUri);
212 Invocation.Builder builder = resourceTarget.request(mediaType);
214 MultivaluedMap<String, Object> newHeaders = new MultivaluedHashMap<String, Object>();
216 for (Map.Entry<String, List<Object>> entry : request.getHeaders().entrySet()) {
217 if (HttpHeaders.AUTHORIZATION.equals(entry.getKey())) {
220 newHeaders.put(entry.getKey(), entry.getValue());
223 newHeaders.add(HttpHeaders.AUTHORIZATION, newAuthorizationHeader);
224 builder.headers(newHeaders);
226 builder.property(REQUEST_PROPERTY_FILTER_REUSED, "true");
228 Invocation invocation;
229 if (request.getEntity() == null) {
230 invocation = builder.build(method);
232 invocation = builder.build(method, Entity.entity(request.getEntity(), request.getMediaType()));
234 Response nextResponse = invocation.invoke();
236 if (nextResponse.hasEntity()) {
237 response.setEntityStream(nextResponse.readEntity(InputStream.class));
239 MultivaluedMap<String, String> headers = response.getHeaders();
241 headers.putAll(nextResponse.getStringHeaders());
242 response.setStatus(nextResponse.getStatus());
244 return response.getStatus() != Response.Status.UNAUTHORIZED.getStatusCode();
248 * Credentials (username + password).
250 static class Credentials {
252 private final String username;
253 private final byte[] password;
256 * Create a new credentials from username and password as byte array.
258 * @param username Username.
259 * @param password Password as byte array.
261 Credentials(String username, byte[] password) {
262 this.username = username;
263 this.password = password;
267 * Create a new credentials from username and password as {@link String}.
269 * @param username Username.
270 * @param password {@code String} password.
272 Credentials(String username, String password) {
273 this.username = username;
274 this.password = password == null ? null : password.getBytes(CHARACTER_SET);
282 String getUsername() {
287 * Return password as byte array.
289 * @return Password string in byte array representation.
291 byte[] getPassword() {
296 private static Credentials extractCredentials(ClientRequestContext request, Type type) {
297 String usernameKey = null;
298 String passwordKey = null;
300 usernameKey = HttpAuthenticationFeature.HTTP_AUTHENTICATION_USERNAME;
301 passwordKey = HttpAuthenticationFeature.HTTP_AUTHENTICATION_PASSWORD;
302 } else if (type == Type.BASIC) {
303 usernameKey = HttpAuthenticationFeature.HTTP_AUTHENTICATION_BASIC_USERNAME;
304 passwordKey = HttpAuthenticationFeature.HTTP_AUTHENTICATION_BASIC_PASSWORD;
307 String userName = (String) request.getProperty(usernameKey);
308 if (userName != null && !userName.equals("")) {
310 Object password = request.getProperty(passwordKey);
311 if (password instanceof byte[]) {
312 pwdBytes = ((byte[]) password);
313 } else if (password instanceof String) {
314 pwdBytes = ((String) password).getBytes(CHARACTER_SET);
316 throw new RequestAuthenticationException("Passwort invalid.");
318 return new Credentials(userName, pwdBytes);
324 * Get credentials actual for the current request. Priorities in credentials selection are the following:
326 * <li>Basic/digest specific credentials defined in the request properties</li>
327 * <li>Common credentials defined in the request properties</li>
328 * <li>{@code defaultCredentials}</li>
331 * @param request Request from which credentials should be extracted.
332 * @param defaultCredentials Default credentials (can be {@code null}).
333 * @param type Type of requested credentials.
334 * @return Credentials or {@code null} if no credentials are found and {@code defaultCredentials} are {@code null}.
335 * @throws RequestAuthenticationException in case the {@code username} or {@code password} is invalid.
337 static Credentials getCredentials(ClientRequestContext request, Credentials defaultCredentials, Type type) {
338 Credentials commonCredentials = extractCredentials(request, type);
340 if (commonCredentials != null) {
341 return commonCredentials;
343 Credentials specificCredentials = extractCredentials(request, null);
345 return specificCredentials != null ? specificCredentials : defaultCredentials;