NegotiateAuthentication.java revision 12745:f068a4ffddd2
1/*
2 * Copyright (c) 2005, 2014, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package sun.net.www.protocol.http;
27
28import java.net.URL;
29import java.io.IOException;
30import java.net.Authenticator.RequestorType;
31import java.util.Base64;
32import java.util.HashMap;
33import sun.net.www.HeaderParser;
34import sun.util.logging.PlatformLogger;
35import static sun.net.www.protocol.http.AuthScheme.NEGOTIATE;
36import static sun.net.www.protocol.http.AuthScheme.KERBEROS;
37
38/**
39 * NegotiateAuthentication:
40 *
41 * @author weijun.wang@sun.com
42 * @since 1.6
43 */
44
45class NegotiateAuthentication extends AuthenticationInfo {
46
47    private static final long serialVersionUID = 100L;
48    private static final PlatformLogger logger = HttpURLConnection.getHttpLogger();
49
50    private final HttpCallerInfo hci;
51
52    // These maps are used to manage the GSS availability for diffrent
53    // hosts. The key for both maps is the host name.
54    // <code>supported</code> is set when isSupported is checked,
55    // if it's true, a cached Negotiator is put into <code>cache</code>.
56    // the cache can be used only once, so after the first use, it's cleaned.
57    static HashMap <String, Boolean> supported = null;
58    static HashMap <String, Negotiator> cache = null;
59
60    // The HTTP Negotiate Helper
61    private Negotiator negotiator = null;
62
63   /**
64    * Constructor used for both WWW and proxy entries.
65    * @param hci a schemed object.
66    */
67    public NegotiateAuthentication(HttpCallerInfo hci) {
68        super(RequestorType.PROXY==hci.authType ? PROXY_AUTHENTICATION : SERVER_AUTHENTICATION,
69              hci.scheme.equalsIgnoreCase("Negotiate") ? NEGOTIATE : KERBEROS,
70              hci.url,
71              "");
72        this.hci = hci;
73    }
74
75    /**
76     * @return true if this authentication supports preemptive authorization
77     */
78    @Override
79    public boolean supportsPreemptiveAuthorization() {
80        return false;
81    }
82
83    /**
84     * Find out if the HttpCallerInfo supports Negotiate protocol.
85     * @return true if supported
86     */
87    public static boolean isSupported(HttpCallerInfo hci) {
88        ClassLoader loader = null;
89        try {
90            loader = Thread.currentThread().getContextClassLoader();
91        } catch (SecurityException se) {
92            if (logger.isLoggable(PlatformLogger.Level.FINER)) {
93                logger.finer("NegotiateAuthentication: " +
94                    "Attempt to get the context class loader failed - " + se);
95            }
96        }
97
98        if (loader != null) {
99            // Lock on the class loader instance to avoid the deadlock engaging
100            // the lock in "ClassLoader.loadClass(String, boolean)" method.
101            synchronized (loader) {
102                return isSupportedImpl(hci);
103            }
104        }
105        return isSupportedImpl(hci);
106    }
107
108    /**
109     * Find out if the HttpCallerInfo supports Negotiate protocol. In order to
110     * find out yes or no, an initialization of a Negotiator object against it
111     * is tried. The generated object will be cached under the name of ths
112     * hostname at a success try.<br>
113     *
114     * If this method is called for the second time on an HttpCallerInfo with
115     * the same hostname, the answer is retrieved from cache.
116     *
117     * @return true if supported
118     */
119    private static synchronized boolean isSupportedImpl(HttpCallerInfo hci) {
120        if (supported == null) {
121            supported = new HashMap <String, Boolean>();
122            cache = new HashMap <String, Negotiator>();
123        }
124        String hostname = hci.host;
125        hostname = hostname.toLowerCase();
126        if (supported.containsKey(hostname)) {
127            return supported.get(hostname);
128        }
129
130        Negotiator neg = Negotiator.getNegotiator(hci);
131        if (neg != null) {
132            supported.put(hostname, true);
133            // the only place cache.put is called. here we can make sure
134            // the object is valid and the oneToken inside is not null
135            cache.put(hostname, neg);
136            return true;
137        } else {
138            supported.put(hostname, false);
139            return false;
140        }
141    }
142
143    /**
144     * Not supported. Must use the setHeaders() method
145     */
146    @Override
147    public String getHeaderValue(URL url, String method) {
148        throw new RuntimeException ("getHeaderValue not supported");
149    }
150
151    /**
152     * Check if the header indicates that the current auth. parameters are stale.
153     * If so, then replace the relevant field with the new value
154     * and return true. Otherwise return false.
155     * returning true means the request can be retried with the same userid/password
156     * returning false means we have to go back to the user to ask for a new
157     * username password.
158     */
159    @Override
160    public boolean isAuthorizationStale (String header) {
161        return false; /* should not be called for Negotiate */
162    }
163
164    /**
165     * Set header(s) on the given connection.
166     * @param conn The connection to apply the header(s) to
167     * @param p A source of header values for this connection, not used because
168     *          HeaderParser converts the fields to lower case, use raw instead
169     * @param raw The raw header field.
170     * @return true if all goes well, false if no headers were set.
171     */
172    @Override
173    public synchronized boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) {
174
175        try {
176            String response;
177            byte[] incoming = null;
178            String[] parts = raw.split("\\s+");
179            if (parts.length > 1) {
180                incoming = Base64.getDecoder().decode(parts[1]);
181            }
182            response = hci.scheme + " " + Base64.getEncoder().encodeToString(
183                        incoming==null?firstToken():nextToken(incoming));
184
185            conn.setAuthenticationProperty(getHeaderName(), response);
186            return true;
187        } catch (IOException e) {
188            return false;
189        }
190    }
191
192    /**
193     * return the first token.
194     * @return the token
195     * @throws IOException if <code>Negotiator.getNegotiator()</code> or
196     *                     <code>Negotiator.firstToken()</code> failed.
197     */
198    private byte[] firstToken() throws IOException {
199        negotiator = null;
200        if (cache != null) {
201            synchronized(cache) {
202                negotiator = cache.get(getHost());
203                if (negotiator != null) {
204                    cache.remove(getHost()); // so that it is only used once
205                }
206            }
207        }
208        if (negotiator == null) {
209            negotiator = Negotiator.getNegotiator(hci);
210            if (negotiator == null) {
211                IOException ioe = new IOException("Cannot initialize Negotiator");
212                throw ioe;
213            }
214        }
215
216        return negotiator.firstToken();
217    }
218
219    /**
220     * return more tokens
221     * @param token the token to be fed into <code>negotiator.nextToken()</code>
222     * @return the token
223     * @throws IOException if <code>negotiator.nextToken()</code> throws Exception.
224     *  May happen if the input token is invalid.
225     */
226    private byte[] nextToken(byte[] token) throws IOException {
227        return negotiator.nextToken(token);
228    }
229
230    // MS will send a final WWW-Authenticate even if the status is already
231    // 200 OK. The token can be fed into initSecContext() again to determine
232    // if the server can be trusted. This is not the same concept as Digest's
233    // Authentication-Info header.
234    //
235    // Currently we ignore this header.
236
237}
238