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}