001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.http.header;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020
021import java.util.*;
022import java.util.function.*;
023
024/**
025 * Represents an HTTP <c>WWW-Authenticate</c> header.
026 *
027 * <p>
028 * Indicates the authentication scheme that should be used to access the requested entity.
029 *
030 * <h5 class='section'>See Also:</h5><ul>
031 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/NextGenRestClient">Next-Generation REST Client</a>
032 * </ul>
033 *
034 * @since 9.2.1
035 */
036@SuppressWarnings({
037   "java:S1192" // Duplicated "value" literals are HTTP header component keys; a constant would obscure the header grammar.
038})
039public class WwwAuthenticate extends HttpStringHeader {
040
041   /** The header name */
042   public static final String NAME = "WWW-Authenticate";
043
044   /**
045    * Constructor with an eager value.
046    *
047    * @param value The header value. May be <jk>null</jk>.
048    */
049   public WwwAuthenticate(String value) {
050      super(NAME, value);
051   }
052
053   /**
054    * Constructor with a lazy value supplier.
055    *
056    * @param valueSupplier Supplier for the header value. Must not be <jk>null</jk>.
057    */
058   public WwwAuthenticate(Supplier<String> valueSupplier) {
059      super(NAME, valueSupplier);
060   }
061
062   /**
063    * Static factory method with an eager value.
064    *
065    * @param value The header value. May be <jk>null</jk>.
066    * @return A new instance. Never <jk>null</jk>.
067    */
068   public static WwwAuthenticate of(String value) {
069      return new WwwAuthenticate(value);
070   }
071
072   /**
073    * Static factory method with a lazy value supplier.
074    *
075    * @param valueSupplier Supplier for the header value. Must not be <jk>null</jk>.
076    * @return A new instance. Never <jk>null</jk>.
077    */
078   public static WwwAuthenticate of(Supplier<String> valueSupplier) {
079      return new WwwAuthenticate(valueSupplier);
080   }
081
082   /**
083    * Creates a new empty {@link Builder}.
084    *
085    * @return A new builder. Never <jk>null</jk>.
086    */
087   public static Builder create() {
088      return new Builder();
089   }
090
091   /**
092    * Creates a {@link Builder} pre-populated with a <c>Basic</c> challenge for the given realm.
093    *
094    * @param realm The protection space realm. Must not be <jk>null</jk>.
095    * @return A new builder. Never <jk>null</jk>.
096    */
097   public static Builder basic(String realm) {
098      return create().basic(realm);
099   }
100
101   /**
102    * Creates a {@link Builder} pre-populated with a <c>Bearer</c> challenge.
103    *
104    * @return A new builder. Never <jk>null</jk>.
105    */
106   public static Builder bearer() {
107      return create().bearer();
108   }
109
110   /**
111    * Creates a {@link Builder} pre-populated with a <c>Digest</c> challenge.
112    *
113    * @return A new builder. Never <jk>null</jk>.
114    */
115   public static Builder digest() {
116      return create().digest();
117   }
118
119   //------------------------------------------------------------------------------------------------------------------
120   // Inner types
121   //------------------------------------------------------------------------------------------------------------------
122
123   /**
124    * Fluent builder for assembling an HTTP <c>WWW-Authenticate</c> challenge value.
125    *
126    * <p>
127    * Composes the <c>scheme [ 1*SP #auth-param ]</c> challenge form defined by
128    * <a class='doclink' href='https://www.rfc-editor.org/rfc/rfc7235#section-4.1'>RFC 7235 ยง4.1</a>.  Typed setters
129    * cover the common Basic / Bearer / Digest auth-params and apply the correct quoting (quoted-string vs token);
130    * {@link #param(String, String)} is a generic escape hatch so new params never require an API change.  The scheme
131    * presets ({@link #basic(String)} / {@link #bearer()} / {@link #digest()}) overwrite the scheme; auth-param setters
132    * overwrite a previously-set param of the same name while preserving its original position.
133    *
134    * <h5 class='section'>Example:</h5>
135    * <p class='bjava'>
136    *    String <jv>value</jv> = WwwAuthenticate.<jsm>create</jsm>()
137    *       .digest()
138    *       .realm(<js>"http-auth@example.org"</js>)
139    *       .qop(<js>"auth"</js>, <js>"auth-int"</js>)
140    *       .nonce(<js>"7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v"</js>)
141    *       .algorithm(<js>"SHA-256"</js>)
142    *       .build();
143    *    <jc>// =&gt; "Digest realm=\"http-auth@example.org\", qop=\"auth,auth-int\", nonce=\"...\", algorithm=SHA-256"</jc>
144    * </p>
145    *
146    * <h5 class='section'>See Also:</h5><ul>
147    *    <li class='jc'>{@link WwwAuthenticate}
148    *    <li class='extlink'><a class='doclink' href='https://www.rfc-editor.org/rfc/rfc7235'>RFC 7235 - HTTP/1.1 Authentication</a>
149    * </ul>
150    *
151    * @since 10.0.0
152    */
153   public static class Builder {
154
155      private String scheme;
156      private final Map<String,String> params = m();
157
158      /**
159       * Sets the authentication scheme (e.g. <js>"Basic"</js>, <js>"Bearer"</js>, <js>"Digest"</js>).
160       *
161       * @param value The scheme name. Must not be <jk>null</jk> or blank.
162       * @return This object.
163       * @throws IllegalArgumentException If {@code value} is <jk>null</jk> or blank.
164       */
165      public Builder scheme(String value) {
166         reqnn("value", value);
167         var v = value.trim();
168         if (v.isEmpty())
169            throw iaex("scheme must not be blank");
170         scheme = v;
171         return this;
172      }
173
174      /**
175       * Sets the scheme to <c>Basic</c> and the <c>realm</c> auth-param.
176       *
177       * @param realm The protection space realm. Must not be <jk>null</jk>.
178       * @return This object.
179       */
180      public Builder basic(String realm) {
181         return scheme("Basic").realm(realm);
182      }
183
184      /**
185       * Sets the scheme to <c>Bearer</c>.
186       *
187       * @return This object.
188       */
189      public Builder bearer() {
190         return scheme("Bearer");
191      }
192
193      /**
194       * Sets the scheme to <c>Digest</c>.
195       *
196       * @return This object.
197       */
198      public Builder digest() {
199         return scheme("Digest");
200      }
201
202      /**
203       * Sets the <c>realm</c> auth-param (rendered as a quoted-string).
204       *
205       * @param value The realm value. Must not be <jk>null</jk>.
206       * @return This object.
207       */
208      public Builder realm(String value) {
209         return quotedParam("realm", value);
210      }
211
212      /**
213       * Sets the <c>charset</c> auth-param (rendered as a quoted-string).
214       *
215       * @param value The charset (e.g. <js>"UTF-8"</js>). Must not be <jk>null</jk>.
216       * @return This object.
217       */
218      public Builder charset(String value) {
219         return quotedParam("charset", value);
220      }
221
222      /**
223       * Sets the <c>qop</c> auth-param to a comma-separated, quoted list of quality-of-protection tokens.
224       *
225       * @param values The qop tokens (e.g. <js>"auth"</js>, <js>"auth-int"</js>). Must not be <jk>null</jk>.
226       * @return This object.
227       */
228      public Builder qop(String...values) {
229         reqnn("values", values);
230         return quotedParam("qop", String.join(",", values));
231      }
232
233      /**
234       * Sets the <c>nonce</c> auth-param (rendered as a quoted-string).
235       *
236       * @param value The server nonce. Must not be <jk>null</jk>.
237       * @return This object.
238       */
239      public Builder nonce(String value) {
240         return quotedParam("nonce", value);
241      }
242
243      /**
244       * Sets the <c>opaque</c> auth-param (rendered as a quoted-string).
245       *
246       * @param value The opaque value to be echoed back by the client. Must not be <jk>null</jk>.
247       * @return This object.
248       */
249      public Builder opaque(String value) {
250         return quotedParam("opaque", value);
251      }
252
253      /**
254       * Sets the <c>algorithm</c> auth-param (rendered as a bare token, unquoted).
255       *
256       * @param value The algorithm (e.g. <js>"MD5"</js>, <js>"SHA-256"</js>). Must not be <jk>null</jk>.
257       * @return This object.
258       */
259      public Builder algorithm(String value) {
260         return tokenParam("algorithm", value);
261      }
262
263      /**
264       * Sets the <c>domain</c> auth-param to a space-separated, quoted list of URIs.
265       *
266       * @param values The protection space URIs. Must not be <jk>null</jk>.
267       * @return This object.
268       */
269      public Builder domain(String...values) {
270         reqnn("values", values);
271         return quotedParam("domain", String.join(" ", values));
272      }
273
274      /**
275       * Sets the <c>stale</c> auth-param (rendered as a bare token, unquoted).
276       *
277       * @param value <jk>true</jk> if the previous request's nonce was stale.
278       * @return This object.
279       */
280      public Builder stale(boolean value) {
281         return tokenParam("stale", Boolean.toString(value));
282      }
283
284      /**
285       * Sets the <c>error</c> auth-param (rendered as a quoted-string; RFC 6750 Bearer).
286       *
287       * @param value The error code (e.g. <js>"invalid_token"</js>). Must not be <jk>null</jk>.
288       * @return This object.
289       */
290      public Builder error(String value) {
291         return quotedParam("error", value);
292      }
293
294      /**
295       * Sets the <c>error_description</c> auth-param (rendered as a quoted-string; RFC 6750 Bearer).
296       *
297       * @param value The human-readable error description. Must not be <jk>null</jk>.
298       * @return This object.
299       */
300      public Builder errorDescription(String value) {
301         return quotedParam("error_description", value);
302      }
303
304      /**
305       * Generic escape hatch for setting any auth-param by name, rendered as a quoted-string.
306       *
307       * <p>
308       * Replaces any previously-registered value for the same param while preserving its original position.
309       *
310       * @param name The auth-param name. Must not be <jk>null</jk> or blank.
311       * @param value The auth-param value (quoted automatically). Must not be <jk>null</jk>.
312       * @return This object.
313       * @throws IllegalArgumentException If {@code name} is <jk>null</jk> or blank.
314       */
315      public Builder param(String name, String value) {
316         return quotedParam(name, value);
317      }
318
319      private Builder quotedParam(String name, String value) {
320         reqnn("value", value);
321         params.put(cleanName(name), q(value));
322         return this;
323      }
324
325      private Builder tokenParam(String name, String value) {
326         reqnn("value", value);
327         params.put(cleanName(name), value);
328         return this;
329      }
330
331      private static String cleanName(String name) {
332         reqnn("name", name);
333         var n = name.trim();
334         if (n.isEmpty())
335            throw iaex("auth-param name must not be blank");
336         return n;
337      }
338
339      private static String q(String value) {
340         var sb = new StringBuilder(value.length() + 2);
341         sb.append('"');
342         for (var i = 0; i < value.length(); i++) {
343            var c = value.charAt(i);
344            if (c == '\\' || c == '"')
345               sb.append('\\');
346            sb.append(c);
347         }
348         sb.append('"');
349         return sb.toString();
350      }
351
352      /**
353       * Builds the rendered <c>WWW-Authenticate</c> challenge value.
354       *
355       * <p>
356       * Returns an empty string when no scheme or params have been registered.
357       *
358       * @return The header value. Never <jk>null</jk>.
359       */
360      public String build() {
361         if (scheme == null && params.isEmpty())
362            return "";
363         var sb = new StringBuilder();
364         if (scheme != null)
365            sb.append(scheme);
366         if (! params.isEmpty()) {
367            if (scheme != null)
368               sb.append(' ');
369            var parts = new ArrayList<String>(params.size());
370            params.forEach((k, v) -> parts.add(k + "=" + v));
371            sb.append(String.join(", ", parts));
372         }
373         return sb.toString();
374      }
375
376      /**
377       * Builds a {@link WwwAuthenticate} header bean directly from this builder.
378       *
379       * @return A {@link WwwAuthenticate} carrying {@link #build()}. Never <jk>null</jk>.
380       */
381      public WwwAuthenticate toHeader() {
382         return WwwAuthenticate.of(build());
383      }
384
385      @Override
386      public String toString() {
387         return build();
388      }
389   }
390}