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.rest.server.convention;
018
019import java.io.*;
020
021import org.apache.juneau.http.response.*;
022import org.apache.juneau.rest.server.*;
023import org.apache.juneau.rest.server.servlet.*;
024
025/**
026 * Mixin that serves <a href="https://www.rfc-editor.org/rfc/rfc8615">RFC 8615</a>
027 * {@code /.well-known/*} discovery endpoints.
028 *
029 * <p>
030 * Sibling of {@link FaviconMixin}, {@link SeoMixin}, and
031 * {@link VersionMixin}. All four classes live in the
032 * {@code org.apache.juneau.rest.server.convention} convention-endpoints mixin pack.
033 *
034 * <p>
035 * In v1 the mixin mounts a single literal path &mdash; {@code /.well-known/security.txt} per
036 * <a href="https://www.rfc-editor.org/rfc/rfc9116">RFC 9116</a>. The class is structured so that
037 * future entries (for example {@code /.well-known/openid-configuration} from a separate AuthN
038 * mixin pack) can be added without touching this source: each future entry ships its own mixin
039 * class with its own {@link Rest#paths() @Rest(paths)} declaration.
040 *
041 * <p>
042 * Compose into a host resource via
043 * {@link Rest#mixins() @Rest(mixins=WellKnownMixin.class)}; the
044 * {@code /.well-known/security.txt} URL becomes available alongside the host's own endpoints with
045 * no further wiring.
046 *
047 * <h5 class='section'>Hardcoded mount path:</h5>
048 *
049 * <p>
050 * Unlike the sibling api-docs and ops mixins (see {@link org.apache.juneau.rest.server.docs.SwaggerMixin},
051 * {@link VersionMixin}, etc.), the mount path here is <b>not</b> SVL-configurable
052 * &mdash; {@code /.well-known/security.txt} is fixed by
053 * <a href="https://www.rfc-editor.org/rfc/rfc8615">RFC 8615</a> (Well-Known URIs) and
054 * <a href="https://www.rfc-editor.org/rfc/rfc9116">RFC 9116</a> (security.txt). Both RFCs are
055 * explicit that the {@code /.well-known/} prefix is the discovery convention and that the
056 * filename suffix is the protocol-defined registry key &mdash; a runtime override here would
057 * make the endpoint undiscoverable by spec-compliant clients.
058 *
059 * <h5 class='section'>Mixin-only deployment:</h5>
060 *
061 * <p>
062 * This resource is designed for composition via {@code @Rest(mixins=...)}. The mount path is
063 * pinned at the op level by {@link RestGet @RestGet(path="/.well-known/security.txt")} on
064 * {@link #getSecurityTxt}; a class-level {@code @Rest(paths=...)} declaration would be silently
065 * ignored under the mixin pattern (see {@link Rest#paths() @Rest(paths)} Javadoc).
066 *
067 * <h5 class='figure'>Composition example:</h5>
068 *
069 * <p class='bjava'>
070 *    <ja>@Rest</ja>(path=<js>"/api"</js>, mixins=WellKnownMixin.<jk>class</jk>)
071 *    <jk>public class</jk> ApiResource <jk>extends</jk> RestServlet { }
072 *
073 *    <jc>// Configure the security.txt body via a @Bean factory:</jc>
074 *    <ja>@Bean</ja> WellKnownMixin wellKnown() {
075 *       <jk>return</jk> WellKnownMixin.<jsm>create</jsm>()
076 *          .securityTxt(<js>"Contact: security@example.com\nExpires: 2027-01-01T00:00:00Z\n"</js>)
077 *          .build();
078 *    }
079 * </p>
080 *
081 * <h5 class='section'>Defaults &amp; behavior:</h5>
082 *
083 * <ul class='spaced-list'>
084 *    <li><b>No default body.</b> Per RFC 9116, {@code security.txt}'s mere presence is meaningful;
085 *       shipping a placeholder body would be misleading. If the builder is not given a body via
086 *       {@link Builder#securityTxt(String) securityTxt(String)}, the endpoint returns
087 *       {@code 404 Not Found} (i.e. "we don't have one"). Loud documentation in topic page.
088 *    <li><b>Content-Type.</b> {@code text/plain; charset=UTF-8} when a body is configured.
089 *    <li><b>Excluded from generated Swagger / OpenAPI specs</b> via
090 *       {@link OpSwagger#ignore() @OpSwagger(ignore=true)}.
091 * </ul>
092 *
093 * <h5 class='section'>See Also:</h5><ul>
094 *    <li class='jc'>{@link FaviconMixin}
095 *    <li class='jc'>{@link SeoMixin}
096 *    <li class='jc'>{@link VersionMixin}
097 *    <li class='link'><a class="doclink" href="https://www.rfc-editor.org/rfc/rfc8615">RFC 8615 &mdash; Well-Known URIs</a>
098 *    <li class='link'><a class="doclink" href="https://www.rfc-editor.org/rfc/rfc9116">RFC 9116 &mdash; security.txt</a>
099 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerComposition">REST Server — Mixins and Multi-Mount Paths</a>
100 * </ul>
101 *
102 * @since 10.0.0
103 */
104// @formatter:off
105@Rest
106public class WellKnownMixin extends RestMixin {
107
108   /**
109    * Creates a new builder.
110    *
111    * @return A new builder.
112    */
113   public static Builder create() {
114      return new Builder();
115   }
116
117   private final String securityTxt;
118
119   /** No-arg constructor &mdash; results in a 404 on {@code /.well-known/security.txt}. */
120   public WellKnownMixin() {
121      this(create());
122   }
123
124   /**
125    * Builder constructor.
126    *
127    * <p>
128    * Stashes the programmatic {@link RestBuilder}(the builder itself, carrying any {@code @Rest}-level
129    * overrides such as {@code path}) via {@code super(builder)} so those values take precedence over this
130    * mixin's own {@link Rest @Rest} annotation.
131    *
132    * @param builder The builder. Must not be <jk>null</jk>.
133    */
134   protected WellKnownMixin(Builder builder) {
135      super(builder);
136      securityTxt = builder.securityTxt;
137   }
138
139   /**
140    * [GET /.well-known/security.txt] &mdash; emit the configured RFC 9116 body, or 404 when
141    * unset.
142    *
143    * @param res The current REST response.
144    * @throws IOException If an I/O error occurs while writing the response.
145    * @throws NotFound If no {@code security.txt} body is configured.
146    */
147   @RestGet(
148      path="/.well-known/security.txt",
149      summary="security.txt",
150      description="RFC 9116 security.txt — disclosure / contact info for the operator.",
151      swagger=@OpSwagger(ignore=true)
152   )
153   public void getSecurityTxt(RestResponse res) throws IOException {
154      if (securityTxt == null)
155         throw new NotFound();
156      try (var w = res.getDirectWriter("text/plain; charset=UTF-8")) {
157         w.write(securityTxt);
158      }
159   }
160
161   /**
162    * Returns the configured {@code security.txt} body (test/inspection helper).
163    *
164    * @return The body, or <jk>null</jk> if unset.
165    */
166   public String getSecurityTxtBody() {
167      return securityTxt;
168   }
169
170   /**
171    * Builder for {@link WellKnownMixin} instances.
172    *
173    * <p>
174    * Extends {@link org.apache.juneau.rest.server.servlet.RestMixin.Builder RestMixin.Builder} so the mixin's bespoke {@link #securityTxt(String)}
175    * setter chains with true covariant returns alongside the inherited {@link RestBuilder}surface (e.g.
176    * {@code path}, {@code roleGuard}).
177    */
178   public static class Builder extends RestMixin.Builder<WellKnownMixin,Builder> {
179
180      private String securityTxt;
181
182      /** Constructor &mdash; package access for {@link WellKnownMixin#create()}. */
183      protected Builder() {
184         super(WellKnownMixin.class);
185      }
186
187      /**
188       * Sets the {@code security.txt} body that will be served at
189       * {@code /.well-known/security.txt}.
190       *
191       * <p>
192       * Per RFC 9116, the body must include at least one {@code Contact:} field and an
193       * {@code Expires:} field; the mixin does not validate the content &mdash; that's the
194       * caller's responsibility.
195       *
196       * @param value The body content. Must not be <jk>null</jk> or blank.
197       * @return This object.
198       */
199      public Builder securityTxt(String value) {
200         securityTxt = value;
201         return this;
202      }
203
204      /**
205       * Builds a {@link WellKnownMixin} instance.
206       *
207       * @return A configured instance.
208       */
209      @Override /* AbstractRestBuilder */
210      public WellKnownMixin build() {
211         return new WellKnownMixin(this);
212      }
213   }
214}