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 — 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 — 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> — 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 — 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}