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.console.dev;
018
019import java.security.*;
020import java.util.*;
021
022import org.apache.juneau.commons.inject.*;
023import org.apache.juneau.http.*;
024import org.apache.juneau.http.response.*;
025import org.apache.juneau.petstore.auth.*;
026import org.apache.juneau.petstore.console.*;
027import org.apache.juneau.petstore.dto.*;
028import org.apache.juneau.rest.server.*;
029import org.apache.juneau.rest.server.auth.*;
030import org.apache.juneau.rest.server.guard.*;
031
032/**
033 * The bearer-token protected API behind the Secure page.
034 *
035 * <p>
036 * Every op is gated by a fail-closed {@link BearerTokenGuard} backed by {@link StubBearerTokenValidator}: no token
037 * or an unknown token is a {@code 401} with a {@code WWW-Authenticate: Bearer realm="petstore"} challenge.  On
038 * success the resolved {@link Principal} reaches ops as an {@link Auth @Auth} parameter.
039 *
040 * <p>
041 * <b>Why a guard rather than a filter chain?</b>  {@code AuthFilterChain} composes several mechanisms and is
042 * fail-open by design, so a downstream guard decides.  With one mechanism, the op-level guard is the whole story.
043 *
044 * <h5 class='section'>Example:</h5>
045 * <p class='bjava'>
046 *    <jc>// One bean gates every op on the resource.</jc>
047 *    <ja>@Bean</ja>
048 *    <jk>public</jk> RestGuardList guards(BeanStore <jv>bs</jv>) {
049 *       <jk>return</jk> RestGuardList.<jsm>create</jsm>(<jv>bs</jv>)
050 *          .append(BearerTokenGuard.<jsm>create</jsm>().realm(<js>"petstore"</js>).validator(<jk>new</jk> StubBearerTokenValidator()).build())
051 *          .build();
052 *    }
053 *
054 *    <jc>// curl -H 'Authorization: Bearer petstore-user' http://localhost:10000/console/dev/secure/api/pets</jc>
055 * </p>
056 */
057@Rest(path="/api", title="Secure API")
058@SuppressWarnings({
059   "java:S110" // Inheritance depth comes from the BasicRestServlet hierarchy, not this page.
060})
061public class SecureApiRest extends PetstoreConsolePage {
062
063   private static final long serialVersionUID = 1L;
064
065   /**
066    * Gates every op on this resource with a bearer-token guard.
067    *
068    * @param bs The bean store.
069    * @return The guard list.
070    */
071   @Bean
072   public RestGuardList guards(BeanStore bs) {
073      return RestGuardList.create(bs)
074         .append(BearerTokenGuard.create().realm("petstore").validator(new StubBearerTokenValidator()).build())
075         .build();
076   }
077
078   /** @return All pets. */
079   @RestGet(path="/pets")
080   public Collection<Pet> pets() {
081      return store().getPets();
082   }
083
084   /**
085    * @param id The pet id.
086    * @return The pet.
087    * @throws NotFound If there is no such pet.
088    */
089   @RestGet(path="/pets/{id}")
090   public Pet pet(@Path("id") long id) {
091      var pet = store().getPet(id);
092      if (pet == null)
093         throw new NotFound("Unknown pet '%s'", id);
094      return pet;
095   }
096
097   /**
098    * @param caller The authenticated principal (never <jk>null</jk>: the guard rejects anonymous calls first).
099    * @return {@code {name: <principal>}}.
100    */
101   @RestGet(path="/whoami")
102   public Map<String,String> whoami(@Auth Principal caller) {
103      return Map.of("name", caller.getName());
104   }
105}