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.converter;
018
019import java.lang.reflect.*;
020import java.util.*;
021import java.util.function.*;
022
023import org.apache.juneau.commons.reflect.*;
024
025/**
026 * Deny-by-default allow-list settings for the {@link Introspectable} converter.
027 *
028 * <p>
029 *    Reflective method dispatch via {@link Introspectable} is <b>off by default</b> on security grounds &mdash;
030 *    unless explicitly allow-listed, invoking arbitrary public methods on a response object via a URL query
031 *    parameter is a dangerous capability to expose over the wire.  A consumer opts in <i>explicitly</i> by
032 *    registering an {@code IntrospectableSettings} bean (built with {@link Builder#allow(Class, String...)
033 *    allow(...)} or {@link Builder#allowAll()}) in the resource's bean store.  When no such bean is present,
034 *    {@link Introspectable} resolves the default (deny-all) and every <c>invokeMethod</c> request is refused
035 *    with an HTTP 500 ({@link org.apache.juneau.marshall.objecttools.MethodNotAllowlistedException}).
036 * </p>
037 *
038 * <h5 class='section'>Example - allow-listing specific methods:</h5>
039 * <p class='bjava'>
040 *    <ja>@Rest</ja>(converters=Introspectable.<jk>class</jk>)
041 *    <jk>public class</jk> MyResource <jk>extends</jk> BasicRestServlet {
042 *
043 *       <ja>@Bean</ja>
044 *       <jk>public</jk> IntrospectableSettings introspectableSettings() {
045 *          <jk>return</jk> IntrospectableSettings.<jsm>create</jsm>()
046 *             .allow(MyBean.<jk>class</jk>, <js>"getName"</js>, <js>"getAge"</js>)
047 *             .build();
048 *       }
049 *    }
050 * </p>
051 *
052 * <h5 class='section'>Example - trusted resource, pre-10.0 behavior:</h5>
053 * <p class='bjava'>
054 *    <ja>@Bean</ja>
055 *    <jk>public</jk> IntrospectableSettings introspectableSettings() {
056 *       <jk>return</jk> IntrospectableSettings.<jsm>create</jsm>().allowAll().build();
057 *    }
058 * </p>
059 * <p>
060 *    <b>Never</b> call {@link Builder#allowAll() allowAll()} on a resource whose response objects expose
061 *    methods you wouldn't want an arbitrary caller to invoke &mdash; allow-list the specific methods instead.
062 * </p>
063 *
064 * <h5 class='section'>See Also:</h5><ul>
065 *    <li class='jc'>{@link Introspectable}
066 *    <li class='jc'>{@link org.apache.juneau.marshall.objecttools.ObjectIntrospector}
067 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/ObjectTools">Object Tools</a>
068 * </ul>
069 *
070 * @since 10.0.0
071 */
072public class IntrospectableSettings {
073
074   /** The default (deny-all) settings used when no bean is registered. */
075   public static final IntrospectableSettings DEFAULT = create().build();
076
077   private final Predicate<Method> allowed;
078
079   private IntrospectableSettings(Builder b) {
080      this.allowed = b.allowed;
081   }
082
083   /**
084    * Builder creator.
085    *
086    * @return A new builder (deny-all).
087    */
088   public static Builder create() {
089      return new Builder();
090   }
091
092   /**
093    * Returns the effective allow-list filter for these settings.
094    *
095    * @return
096    *    A filter that returns <jk>true</jk> for methods that may be invoked.  Never <jk>null</jk> &mdash;
097    *    returns <jk>false</jk> for every method when nothing has been allow-listed (the default).
098    */
099   public Predicate<Method> asFilter() {
100      return allowed == null ? m -> false : allowed;
101   }
102
103   /**
104    * Builder for {@link IntrospectableSettings}.
105    */
106   public static class Builder {
107      private Predicate<Method> allowed;
108
109      /**
110       * Allow-lists methods matching the specified filter.
111       *
112       * <p>
113       * Can be called multiple times; the filters are OR'ed together, so a method is allowed if it matches
114       * <b>any</b> filter that was added.
115       *
116       * @param filter Filter that returns <jk>true</jk> for methods that may be invoked.
117       * @return This object.
118       */
119      public Builder allow(Predicate<Method> filter) {
120         if (filter != null)
121            allowed = (allowed == null) ? filter : allowed.or(filter);
122         return this;
123      }
124
125      /**
126       * Allow-lists specific method signatures declared on (or inherited by) the specified class.
127       *
128       * <h5 class='section'>Example:</h5>
129       * <p class='bjava'>
130       *    IntrospectableSettings.<jsm>create</jsm>().allow(MyBean.<jk>class</jk>, <js>"getName"</js>, <js>"getAge"</js>);
131       * </p>
132       *
133       * @param declaringClass The class the allow-listed methods must be declared on (or a subtype thereof).
134       * @param signatures
135       *    One or more method signatures as returned by {@link MethodInfo#getSignature()} (e.g. <js>"getName"</js>,
136       *    <js>"substring(int,int)"</js>).
137       * @return This object.
138       */
139      public Builder allow(Class<?> declaringClass, String...signatures) {
140         var sigs = Set.of(signatures);
141         return allow(m -> declaringClass.isAssignableFrom(m.getDeclaringClass()) && sigs.contains(MethodInfo.of(m).getSignature()));
142      }
143
144      /**
145       * Allow-lists any public, non-deprecated method &mdash; the pre-10.0 behavior.
146       *
147       * <p>
148       * <b>Use only for trusted resources</b> whose response objects' full public API surface is one you're
149       * comfortable exposing to any caller who can reach this endpoint, since the method name and arguments
150       * for {@link Introspectable} always come from untrusted request query parameters.
151       *
152       * @return This object.
153       */
154      public Builder allowAll() {
155         allowed = m -> true;
156         return this;
157      }
158
159      /**
160       * Builds the settings.
161       *
162       * @return A new {@link IntrospectableSettings}.
163       */
164      public IntrospectableSettings build() {
165         return new IntrospectableSettings(this);
166      }
167   }
168}