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.nio.charset.*;
022import java.util.*;
023import java.util.function.*;
024
025/**
026 * Represents an HTTP <c>Authorization</c> header.
027 *
028 * <p>
029 * Authentication credentials for HTTP authentication.
030 *
031 * <h5 class='section'>See Also:</h5><ul>
032 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/NextGenRestClient">Next-Generation REST Client</a>
033 * </ul>
034 *
035 * @since 9.2.1
036 */
037@SuppressWarnings({
038   "java:S1192" // Duplicated "value" literals are HTTP header component keys; a constant would obscure the header grammar.
039})
040public class Authorization extends HttpStringHeader {
041
042   /** The header name */
043   public static final String NAME = "Authorization";
044
045   /**
046    * Constructor with an eager value.
047    *
048    * @param value The header value. May be <jk>null</jk>.
049    */
050   public Authorization(String value) {
051      super(NAME, value);
052   }
053
054   /**
055    * Constructor with a lazy value supplier.
056    *
057    * @param valueSupplier Supplier for the header value. Must not be <jk>null</jk>.
058    */
059   public Authorization(Supplier<String> valueSupplier) {
060      super(NAME, valueSupplier);
061   }
062
063   /**
064    * Static factory method with an eager value.
065    *
066    * @param value The header value. May be <jk>null</jk>.
067    * @return A new instance. Never <jk>null</jk>.
068    */
069   public static Authorization of(String value) {
070      return new Authorization(value);
071   }
072
073   /**
074    * Static factory method with a lazy value supplier.
075    *
076    * @param valueSupplier Supplier for the header value. Must not be <jk>null</jk>.
077    * @return A new instance. Never <jk>null</jk>.
078    */
079   public static Authorization of(Supplier<String> valueSupplier) {
080      return new Authorization(valueSupplier);
081   }
082
083   /**
084    * Creates a new empty {@link Builder}.
085    *
086    * @return A new builder. Never <jk>null</jk>.
087    */
088   public static Builder create() {
089      return new Builder();
090   }
091
092   /**
093    * Creates a {@link Builder} pre-populated with <c>Basic</c> credentials (base64 of <c>user:password</c>).
094    *
095    * @param user The username. Must not be <jk>null</jk>.
096    * @param password The password. Must not be <jk>null</jk>.
097    * @return A new builder. Never <jk>null</jk>.
098    */
099   public static Builder basic(String user, String password) {
100      return create().basic(user, password);
101   }
102
103   /**
104    * Creates a {@link Builder} pre-populated with <c>Bearer</c> credentials.
105    *
106    * @param token The bearer token. Must not be <jk>null</jk>.
107    * @return A new builder. Never <jk>null</jk>.
108    */
109   public static Builder bearer(String token) {
110      return create().bearer(token);
111   }
112
113   /**
114    * Creates a {@link Builder} pre-populated with a <c>Digest</c> scheme.
115    *
116    * @return A new builder. Never <jk>null</jk>.
117    */
118   public static Builder digest() {
119      return create().digest();
120   }
121
122   //------------------------------------------------------------------------------------------------------------------
123   // Inner types
124   //------------------------------------------------------------------------------------------------------------------
125
126   /**
127    * Fluent builder for assembling an HTTP <c>Authorization</c> credentials value.
128    *
129    * <p>
130    * Composes the <c>scheme 1*SP ( token68 / #auth-param )</c> credentials form defined by
131    * <a class='doclink' href='https://www.rfc-editor.org/rfc/rfc7235#section-4.2'>RFC 7235 ยง4.2</a>.  The
132    * {@link #basic(String, String)} and {@link #bearer(String)} presets render the opaque <c>token68</c> form; the
133    * {@link #digest()} preset enables the typed Digest auth-params, which apply the correct quoting (quoted-string vs
134    * token).  {@link #param(String, String)} is a generic escape hatch so new params never require an API change.
135    * Setting a scheme preset or {@link #token(String)} clears any previously-registered auth-params and vice versa, so
136    * a built value carries either a token68 or an auth-param list, never both.
137    *
138    * <h5 class='section'>Example:</h5>
139    * <p class='bjava'>
140    *    String <jv>value</jv> = Authorization.<jsm>create</jsm>()
141    *       .digest()
142    *       .username(<js>"Mufasa"</js>)
143    *       .realm(<js>"http-auth@example.org"</js>)
144    *       .uri(<js>"/dir/index.html"</js>)
145    *       .qop(<js>"auth"</js>)
146    *       .nc(<js>"00000001"</js>)
147    *       .response(<js>"8ca523f5e9506fed4657c9700eebdbec"</js>)
148    *       .build();
149    *    <jc>// =&gt; "Digest username=\"Mufasa\", realm=\"...\", uri=\"/dir/index.html\", qop=auth, nc=00000001, response=\"...\""</jc>
150    * </p>
151    *
152    * <h5 class='section'>See Also:</h5><ul>
153    *    <li class='jc'>{@link Authorization}
154    *    <li class='extlink'><a class='doclink' href='https://www.rfc-editor.org/rfc/rfc7235'>RFC 7235 - HTTP/1.1 Authentication</a>
155    * </ul>
156    *
157    * @since 10.0.0
158    */
159   public static class Builder {
160
161      private String scheme;
162      private String token68;
163      private final Map<String,String> params = m();
164
165      /**
166       * Sets the authentication scheme (e.g. <js>"Basic"</js>, <js>"Bearer"</js>, <js>"Digest"</js>).
167       *
168       * @param value The scheme name. Must not be <jk>null</jk> or blank.
169       * @return This object.
170       * @throws IllegalArgumentException If {@code value} is <jk>null</jk> or blank.
171       */
172      public Builder scheme(String value) {
173         reqnn("value", value);
174         var v = value.trim();
175         if (v.isEmpty())
176            throw iaex("scheme must not be blank");
177         scheme = v;
178         return this;
179      }
180
181      /**
182       * Sets <c>Basic</c> credentials by base64-encoding <c>user:password</c> as the token68 form.
183       *
184       * @param user The username. Must not be <jk>null</jk>.
185       * @param password The password. Must not be <jk>null</jk>.
186       * @return This object.
187       */
188      public Builder basic(String user, String password) {
189         reqnn("user", user);
190         reqnn("password", password);
191         var encoded = Base64.getEncoder().encodeToString((user + ":" + password).getBytes(StandardCharsets.UTF_8));
192         return scheme("Basic").token(encoded);
193      }
194
195      /**
196       * Sets <c>Bearer</c> credentials with the supplied token as the token68 form.
197       *
198       * @param token The bearer token. Must not be <jk>null</jk>.
199       * @return This object.
200       */
201      public Builder bearer(String token) {
202         return scheme("Bearer").token(token);
203      }
204
205      /**
206       * Sets the scheme to <c>Digest</c> (typed Digest auth-params can then be chained).
207       *
208       * @return This object.
209       */
210      public Builder digest() {
211         return scheme("Digest");
212      }
213
214      /**
215       * Sets the opaque <c>token68</c> credentials form, clearing any auth-params.
216       *
217       * @param value The token68 value. Must not be <jk>null</jk>.
218       * @return This object.
219       */
220      public Builder token(String value) {
221         reqnn("value", value);
222         params.clear();
223         token68 = value;
224         return this;
225      }
226
227      /**
228       * Sets the <c>username</c> auth-param (rendered as a quoted-string).
229       *
230       * @param value The username. Must not be <jk>null</jk>.
231       * @return This object.
232       */
233      public Builder username(String value) {
234         return quotedParam("username", value);
235      }
236
237      /**
238       * Sets the <c>realm</c> auth-param (rendered as a quoted-string).
239       *
240       * @param value The realm. Must not be <jk>null</jk>.
241       * @return This object.
242       */
243      public Builder realm(String value) {
244         return quotedParam("realm", value);
245      }
246
247      /**
248       * Sets the <c>nonce</c> auth-param (rendered as a quoted-string).
249       *
250       * @param value The server nonce echoed back from the challenge. Must not be <jk>null</jk>.
251       * @return This object.
252       */
253      public Builder nonce(String value) {
254         return quotedParam("nonce", value);
255      }
256
257      /**
258       * Sets the <c>uri</c> auth-param (rendered as a quoted-string).
259       *
260       * @param value The effective request URI. Must not be <jk>null</jk>.
261       * @return This object.
262       */
263      public Builder uri(String value) {
264         return quotedParam("uri", value);
265      }
266
267      /**
268       * Sets the <c>response</c> auth-param (rendered as a quoted-string).
269       *
270       * @param value The computed digest response. Must not be <jk>null</jk>.
271       * @return This object.
272       */
273      public Builder response(String value) {
274         return quotedParam("response", value);
275      }
276
277      /**
278       * Sets the <c>cnonce</c> auth-param (rendered as a quoted-string).
279       *
280       * @param value The client nonce. Must not be <jk>null</jk>.
281       * @return This object.
282       */
283      public Builder cnonce(String value) {
284         return quotedParam("cnonce", value);
285      }
286
287      /**
288       * Sets the <c>opaque</c> auth-param (rendered as a quoted-string).
289       *
290       * @param value The opaque value echoed back from the challenge. Must not be <jk>null</jk>.
291       * @return This object.
292       */
293      public Builder opaque(String value) {
294         return quotedParam("opaque", value);
295      }
296
297      /**
298       * Sets the <c>algorithm</c> auth-param (rendered as a bare token, unquoted).
299       *
300       * @param value The algorithm (e.g. <js>"MD5"</js>, <js>"SHA-256"</js>). Must not be <jk>null</jk>.
301       * @return This object.
302       */
303      public Builder algorithm(String value) {
304         return tokenParam("algorithm", value);
305      }
306
307      /**
308       * Sets the <c>qop</c> auth-param (rendered as a bare token, unquoted; a single value in credentials).
309       *
310       * @param value The quality-of-protection token (e.g. <js>"auth"</js>). Must not be <jk>null</jk>.
311       * @return This object.
312       */
313      public Builder qop(String value) {
314         return tokenParam("qop", value);
315      }
316
317      /**
318       * Sets the <c>nc</c> (nonce-count) auth-param (rendered as a bare token, unquoted).
319       *
320       * @param value The 8-digit hex nonce-count (e.g. <js>"00000001"</js>). Must not be <jk>null</jk>.
321       * @return This object.
322       */
323      public Builder nc(String value) {
324         return tokenParam("nc", value);
325      }
326
327      /**
328       * Generic escape hatch for setting any auth-param by name, rendered as a quoted-string.
329       *
330       * <p>
331       * Replaces any previously-registered value for the same param while preserving its original position, and
332       * clears any previously-set {@link #token(String) token68}.
333       *
334       * @param name The auth-param name. Must not be <jk>null</jk> or blank.
335       * @param value The auth-param value (quoted automatically). Must not be <jk>null</jk>.
336       * @return This object.
337       * @throws IllegalArgumentException If {@code name} is <jk>null</jk> or blank.
338       */
339      public Builder param(String name, String value) {
340         return quotedParam(name, value);
341      }
342
343      private Builder quotedParam(String name, String value) {
344         reqnn("value", value);
345         token68 = null;
346         params.put(cleanName(name), q(value));
347         return this;
348      }
349
350      private Builder tokenParam(String name, String value) {
351         reqnn("value", value);
352         token68 = null;
353         params.put(cleanName(name), value);
354         return this;
355      }
356
357      private static String cleanName(String name) {
358         reqnn("name", name);
359         var n = name.trim();
360         if (n.isEmpty())
361            throw iaex("auth-param name must not be blank");
362         return n;
363      }
364
365      private static String q(String value) {
366         var sb = new StringBuilder(value.length() + 2);
367         sb.append('"');
368         for (var i = 0; i < value.length(); i++) {
369            var c = value.charAt(i);
370            if (c == '\\' || c == '"')
371               sb.append('\\');
372            sb.append(c);
373         }
374         sb.append('"');
375         return sb.toString();
376      }
377
378      /**
379       * Builds the rendered <c>Authorization</c> credentials value.
380       *
381       * <p>
382       * Returns an empty string when no scheme has been registered.
383       *
384       * @return The header value. Never <jk>null</jk>.
385       */
386      public String build() {
387         if (scheme == null)
388            return "";
389         var sb = new StringBuilder(scheme);
390         if (token68 != null) {
391            sb.append(' ').append(token68);
392         } else if (! params.isEmpty()) {
393            sb.append(' ');
394            var parts = new ArrayList<String>(params.size());
395            params.forEach((k, v) -> parts.add(k + "=" + v));
396            sb.append(String.join(", ", parts));
397         }
398         return sb.toString();
399      }
400
401      /**
402       * Builds an {@link Authorization} header bean directly from this builder.
403       *
404       * @return An {@link Authorization} carrying {@link #build()}. Never <jk>null</jk>.
405       */
406      public Authorization toHeader() {
407         return Authorization.of(build());
408      }
409
410      @Override
411      public String toString() {
412         return build();
413      }
414   }
415}