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>// => "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}