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;
018
019import java.util.*;
020
021import org.apache.juneau.commons.settings.*;
022import org.apache.juneau.http.response.*;
023import org.apache.juneau.petstore.auth.*;
024import org.apache.juneau.rest.server.*;
025import org.apache.juneau.rest.server.auth.*;
026
027/**
028 * The console's write gate.  Console writes are open by default; with {@code -Dpetstore.secure=true} each one
029 * needs the demo bearer token ({@link StubBearerTokenValidator#DEFAULT_TOKENS}).  Also names the audit actor.
030 *
031 * <h5 class='section'>Example:</h5>
032 * <p class='bjava'>
033 *    <ja>@RestPost</ja>(path=<js>"/"</js>)
034 *    <jk>public</jk> Pet create(RestRequest <jv>req</jv>, <ja>@Content</ja> Pet <jv>pet</jv>) {
035 *       <jk>return</jk> store().createPet(<jv>pet</jv>, ConsoleWrites.<jsm>actor</jsm>(<jv>req</jv>));
036 *    }
037 * </p>
038 */
039public final class ConsoleWrites {
040
041   /** Setting (system property, environment, or any registered source) that turns on the console write gate. */
042   public static final String SECURE_PROPERTY = "petstore.secure";
043
044   private static final StubBearerTokenValidator VALIDATOR = new StubBearerTokenValidator();
045
046   private ConsoleWrites() {}
047
048   /**
049    * The viewer's roles, for {@code <@facts>} and server-side omission of role-gated cards.
050    *
051    * <p>
052    * Never an authorization check: writes still go through {@link #actor(RestRequest)}.  Independent of
053    * {@code petstore.secure}.
054    *
055    * @param req The request.
056    * @return {@code ["admin"]} when the bearer token validates to the {@code admin} principal, else empty.
057    */
058   public static List<String> roles(RestRequest req) {
059      var user = user(req);
060      return "admin".equals(user) ? List.of("admin") : List.of();
061   }
062
063   private static String user(RestRequest req) {
064      var auth = req.getHeaderParam("Authorization").asString().orElse(null);
065      var token = auth != null && auth.regionMatches(true, 0, "Bearer ", 0, 7) ? auth.substring(7).trim() : null;
066      if (token == null)
067         return null;
068      try {
069         return VALIDATOR.validate(token).getName();
070      } catch (@SuppressWarnings("unused") AuthenticationException e) {
071         return null; // Unknown token: treated as anonymous.
072      }
073   }
074
075   /**
076    * Checks the write gate and returns the audit actor.
077    *
078    * @param req The request.
079    * @return {@code "console:<user>"} when a valid bearer token is present, else {@code "console"}.
080    * @throws Unauthorized If the gate is on and the token is missing or unknown.
081    */
082   public static String actor(RestRequest req) {
083      var user = user(req);
084      if (user == null && secure())
085         throw new Unauthorized("Console writes need the demo bearer token").setHeader("WWW-Authenticate", "Bearer realm=\"petstore\"");
086      return user == null ? "console" : "console:" + user;
087   }
088
089   // Resolved on every call, so a changed setting (or a test override) takes effect immediately.
090   private static boolean secure() {
091      return Settings.get().get(SECURE_PROPERTY).asBoolean().orElse(false);
092   }
093
094   /**
095    * The audit actor reduced to the staging author.
096    *
097    * @param actor A {@link #actor(RestRequest)} value.
098    * @return The bare user ({@code "admin"}), or {@code "anonymous"} when writes are unsecured.
099    */
100   public static String author(String actor) {
101      var a = actor.replaceFirst("^console:?", "");
102      return a.isEmpty() ? "anonymous" : a;
103   }
104}