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.petstore.jetty; 018 019import static org.apache.juneau.commons.utils.Shorts.*; 020 021import java.util.*; 022 023import org.apache.juneau.commons.inject.*; 024import org.apache.juneau.commons.settings.*; 025import org.apache.juneau.microservice.*; 026import org.apache.juneau.microservice.examples.*; 027import org.apache.juneau.microservice.jetty.*; 028import org.apache.juneau.petstore.console.*; 029 030import jakarta.servlet.*; 031 032/** 033 * Petstore Jetty/Microservice deployment entry point. 034 * 035 * <p> 036 * Locates {@code juneau-petstore-jetty.cfg} (in the working directory or on the classpath), wires Jetty via 037 * {@link JettyConfiguration}, and mounts {@link RootResources} as the root servlet. 038 * 039 * <h5 class='section'>See Also:</h5><ul> 040 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauPetstore">juneau-petstore</a> 041 * </ul> 042 */ 043public class App { 044 045 /** 046 * Setting (system property, environment, or any registered source) listing the host names the admin resources 047 * answer to: comma-separated, no ports. Unset or blank, only the loopback names {@link AdminBoundary#LOOPBACK_HOSTS} 048 * are answered. Independent of {@value PetstoreApiBoundary#ALLOWED_HOSTS_PROPERTY}. 049 */ 050 public static final String ADMIN_ALLOWED_HOSTS_PROPERTY = "petstore.admin.allowedHosts"; 051 052 /** 053 * Entry point. 054 * 055 * @param args Command-line arguments forwarded to {@link Microservice}. 056 * @throws Exception If the microservice fails to start. 057 */ 058 public static void main(String[] args) throws Exception { 059 // @formatter:off 060 Microservice 061 .create() 062 .args(args) 063 .configurations(JettyConfiguration.class, AppConfig.class) 064 .build() 065 .start() 066 .startConsole() 067 .join(); 068 // @formatter:on 069 } 070 071 /** 072 * Application-specific configuration class contributing the top-level REST servlet and the console, API and admin 073 * boundary filters. 074 */ 075 @Configuration 076 public static class AppConfig { 077 078 // Shared by both boundary beans: the console publishes tokens from the instance the API filter verifies them with. 079 private final PetstoreApiBoundary apiBoundary = new PetstoreApiBoundary(); 080 081 /** 082 * Provides the top-level REST servlet, auto-mounted by {@code JettyServerComponent} at 083 * {@link org.apache.juneau.rest.server.Rest#path()}. 084 * 085 * @return The root servlet. 086 */ 087 @Bean 088 public Servlet rootResources() { 089 return new RootResources(); 090 } 091 092 /** 093 * Provides the console's CSRF boundary, covering {@code /console} only. 094 * 095 * <p> 096 * The {@code /petstore} API is covered by {@link #apiBoundary()} and the admin resources by 097 * {@link #adminBoundary()}; {@code /petstore-ui} is outside all three: see {@link PetstoreConsoleBoundary}. 098 * It runs ahead of routing and ahead of authentication, for the request, forward, async and error dispatches; 099 * the include dispatch is deliberately not mapped. It is built over the same {@link PetstoreApiBoundary} that 100 * {@link #apiBoundary()} mounts, so the API token its pages carry is one the API filter accepts. 101 * 102 * @return The filter registration. 103 */ 104 @Bean(name="consoleBoundary") 105 public ServletFilterRegistration consoleBoundary() { 106 return ServletFilterRegistration.create(new PetstoreConsoleBoundary(apiBoundary)) 107 .urlPatterns(PetstoreConsoleBoundary.URL_PATTERN) 108 .dispatcherTypes(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.ASYNC, DispatcherType.ERROR) 109 .order(ServletFilterRegistration.HIGHEST_PRECEDENCE) 110 .build(); 111 } 112 113 /** 114 * Provides the API's CSRF boundary, covering {@code /petstore/*}. 115 * 116 * <p> 117 * It runs only on the original request: an internal forward or error dispatch to the API is not a new 118 * attacker-controlled request. The order keeps it just behind {@link #consoleBoundary()}; the two patterns never 119 * overlap. 120 * 121 * @return The filter registration. 122 */ 123 @Bean(name="apiBoundary") 124 public ServletFilterRegistration apiBoundary() { 125 return ServletFilterRegistration.create(apiBoundary) 126 .urlPatterns(PetstoreApiBoundary.URL_PATTERN) 127 .dispatcherTypes(DispatcherType.REQUEST) 128 .order(ServletFilterRegistration.HIGHEST_PRECEDENCE + 10) 129 .build(); 130 } 131 132 /** 133 * Provides the admin resources' CSRF boundary, covering {@code /config}, {@code /logs} and {@code /shutdown}. 134 * 135 * <p> 136 * A filter of its own, answering to the host names in {@value App#ADMIN_ALLOWED_HOSTS_PROPERTY}, or to the 137 * loopback names only while that is unset. The admin resources have no forms, so a browser write to them is 138 * refused, and command-line writes are admitted: see {@link AdminBoundary}. The boundary does not authenticate: 139 * the admin resources must not be exposed on an untrusted network. 140 * 141 * @return The filter registration. 142 * @throws IllegalArgumentException If {@value App#ADMIN_ALLOWED_HOSTS_PROPERTY} holds an entry that is blank or 143 * not a bare host name. 144 */ 145 @Bean(name="adminBoundary") 146 public ServletFilterRegistration adminBoundary() { 147 return AdminBoundary.registration("", hosts(Settings.get().get(ADMIN_ALLOWED_HOSTS_PROPERTY).orElse(null))); 148 } 149 150 // The comma-separated entries, trimmed, blank ones kept so the boundary refuses them; none when unset. 151 private static String[] hosts(String setting) { 152 if (ib(setting)) 153 return new String[0]; 154 return Arrays.stream(setting.split(",", -1)).map(String::trim).toArray(String[]::new); 155 } 156 } 157}