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 — {@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 * — {@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 — 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 & 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 — Well-Known URIs</a> 098 * <li class='link'><a class="doclink" href="https://www.rfc-editor.org/rfc/rfc9116">RFC 9116 — 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 — 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] — 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 — 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 — 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}