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}