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 static org.apache.juneau.commons.utils.Shorts.*;
020import static org.apache.juneau.commons.utils.ThrowableUtils.*;
021
022import org.apache.juneau.http.response.*;
023import org.apache.juneau.marshall.*;
024import org.apache.juneau.marshall.json5.*;
025import org.apache.juneau.marshall.objecttools.*;
026import org.apache.juneau.rest.server.*;
027
028/**
029 * Converter for enablement of {@link ObjectIntrospector} support on response objects returned by a
030 * <c>@RestOp</c>-annotated method.
031 *
032 * <p>
033 * When enabled, public methods can be called on objects returned through the {@link RestResponse#setContent(Object)}
034 * method.
035 *
036 * <p>
037 * Note that opening up public methods for calling through a REST interface can be dangerous, and should be done with
038 * caution.
039 *
040 * <p>
041 *    <b>Security - deny-by-default:</b> As of Juneau 10.0, {@link ObjectIntrospector} is secure-by-default and
042 *    denies reflective method dispatch unless the caller has explicitly allow-listed the target method(s).  This
043 *    converter is <b>off by default</b> as shipped &mdash; with no {@link IntrospectableSettings} bean registered,
044 *    every <c>invokeMethod</c> request is refused with an HTTP 500 ({@link MethodNotAllowlistedException} wrapped
045 *    in {@link InternalServerError}).  A resource opts specific methods in <i>explicitly</i> by registering an
046 *    {@link IntrospectableSettings} bean in its bean store:
047 * </p>
048 * <p class='bjava'>
049 *    <ja>@Rest</ja>(converters=Introspectable.<jk>class</jk>)
050 *    <jk>public class</jk> MyResource <jk>extends</jk> BasicRestServlet {
051 *
052 *       <ja>@Bean</ja>
053 *       <jk>public</jk> IntrospectableSettings introspectableSettings() {
054 *          <jk>return</jk> IntrospectableSettings.<jsm>create</jsm>()
055 *             .allow(MyBean.<jk>class</jk>, <js>"getName"</js>, <js>"getAge"</js>)
056 *             .build();
057 *       }
058 *    }
059 * </p>
060 * <p>
061 *    For a trusted resource that needs the pre-10.0 behavior of dispatching to any public, non-deprecated method,
062 *    call {@link IntrospectableSettings.Builder#allowAll() allowAll()} instead &mdash; but never do this on a
063 *    resource whose response objects expose methods you wouldn't want an arbitrary caller to invoke, since the
064 *    method name and arguments always come from untrusted request query parameters.  See
065 *    {@link IntrospectableSettings} for the full builder API.
066 * </p>
067 *
068 * <p>
069 * Java methods are invoked by passing in the following URL parameters:
070 * <ul class='spaced-list'>
071 *    <li>
072 *       <c>&amp;invokeMethod</c> - The Java method name, optionally with arguments if necessary to
073 *       differentiate between methods.
074 *    <li>
075 *       <c>&amp;invokeArgs</c> - The arguments as an array.
076 * </ul>
077 *
078 * <h5 class='section'>See Also:</h5><ul>
079 *    <li class='jc'>{@link ObjectIntrospector} - Additional information on introspection of POJO methods.
080 *    <li class='jc'>{@link IntrospectableSettings} - Per-resource allow-list configuration for this converter.
081 *    <li class='ja'>{@link RestOp#converters()} - Registering converters with REST resources.
082 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/Converters">Converters</a>
083 * </ul>
084 */
085public class Introspectable implements RestConverter {
086
087   /**
088    * Swagger parameters for this converter.
089    */
090   public static final String SWAGGER_PARAMS = """
091      {in:'query',name:'invokeMethod',description:' The Java method name, optionally with arguments if necessary to differentiate between methods.',examples:{example:'toString'}},
092      {in:'query',name:'invokeArgs',description:'The arguments as an array.',examples:{example:'foo,bar'}}
093      """;
094
095   @Override /* Overridden from RestConverter */
096   @SuppressWarnings({
097      "resource" // The bean store is owned by the RestContext; this only borrows a bean and must not close it.
098   })
099   public Object convert(RestRequest req, Object o) throws InternalServerError {
100      String method = req.getQueryParam("invokeMethod").orElse(null);
101      String args = req.getQueryParam("invokeArgs").orElse(null);
102      if (method == null)
103         return o;
104      try {
105         MarshallingSession bs = req.getMarshallingSession();
106         var swap = bs.getClassMetaForObject(o).getSwap(bs);
107         if (nn(swap))
108            o = swap.swap(bs, o);
109         var settings = req.getContext().getBeanStore().getBean(IntrospectableSettings.class).orElse(IntrospectableSettings.DEFAULT);
110         return ObjectIntrospector.create(o, Json5Parser.DEFAULT).allow(settings.asFilter()).invokeMethod(method, args);
111      } catch (Exception e) {
112         return new InternalServerError(e, "Error occurred trying to invoke method: %s", localizedMessage(e));
113      }
114   }
115}