1 /* 2 * Copyright 2023 Google LLC 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * 15 * * Neither the name of Google LLC nor the names of its 16 * contributors may be used to endorse or promote products derived from 17 * this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 package com.google.auth.oauth2; 33 34 import static com.google.auth.oauth2.ExternalAccountCredentials.EXECUTABLE_SOURCE_KEY; 35 36 import java.math.BigDecimal; 37 import java.util.Map; 38 import javax.annotation.Nullable; 39 40 /** 41 * Encapsulates the credential source portion of the configuration for PluggableAuthCredentials. 42 * 43 * <p>Command is the only required field. If timeout_millis is not specified, the library will 44 * default to a 30 second timeout. 45 * 46 * <pre> 47 * Sample credential source for Pluggable Auth credentials: 48 * { 49 * ... 50 * "credential_source": { 51 * "executable": { 52 * "command": "/path/to/get/credentials.sh --arg1=value1 --arg2=value2", 53 * "timeout_millis": 5000, 54 * "output_file": "/path/to/generated/cached/credentials" 55 * } 56 * } 57 * } 58 * </pre> 59 */ 60 public class PluggableAuthCredentialSource extends ExternalAccountCredentials.CredentialSource { 61 62 // The default timeout for waiting for the executable to finish (30 seconds). 63 static final int DEFAULT_EXECUTABLE_TIMEOUT_MS = 30 * 1000; 64 // The minimum timeout for waiting for the executable to finish (5 seconds). 65 static final int MINIMUM_EXECUTABLE_TIMEOUT_MS = 5 * 1000; 66 // The maximum timeout for waiting for the executable to finish (120 seconds). 67 static final int MAXIMUM_EXECUTABLE_TIMEOUT_MS = 120 * 1000; 68 69 static final String COMMAND_KEY = "command"; 70 static final String TIMEOUT_MILLIS_KEY = "timeout_millis"; 71 static final String OUTPUT_FILE_KEY = "output_file"; 72 73 // Required. The command used to retrieve the 3rd party token. 74 final String executableCommand; 75 76 // Optional. Set to the default timeout when not provided. 77 final int executableTimeoutMs; 78 79 // Optional. Provided when the 3rd party executable caches the response at the specified 80 // location. 81 @Nullable final String outputFilePath; 82 83 @SuppressWarnings("unchecked") PluggableAuthCredentialSource(Map<String, Object> credentialSourceMap)84 public PluggableAuthCredentialSource(Map<String, Object> credentialSourceMap) { 85 super(credentialSourceMap); 86 87 if (!credentialSourceMap.containsKey(EXECUTABLE_SOURCE_KEY)) { 88 throw new IllegalArgumentException( 89 "Invalid credential source for PluggableAuth credentials."); 90 } 91 92 Map<String, Object> executable = 93 (Map<String, Object>) credentialSourceMap.get(EXECUTABLE_SOURCE_KEY); 94 95 // Command is the only required field. 96 if (!executable.containsKey(COMMAND_KEY)) { 97 throw new IllegalArgumentException( 98 "The PluggableAuthCredentialSource is missing the required 'command' field."); 99 } 100 101 // Parse the executable timeout. 102 if (executable.containsKey(TIMEOUT_MILLIS_KEY)) { 103 Object timeout = executable.get(TIMEOUT_MILLIS_KEY); 104 if (timeout instanceof BigDecimal) { 105 executableTimeoutMs = ((BigDecimal) timeout).intValue(); 106 } else if (executable.get(TIMEOUT_MILLIS_KEY) instanceof Integer) { 107 executableTimeoutMs = (int) timeout; 108 } else { 109 executableTimeoutMs = Integer.parseInt((String) timeout); 110 } 111 } else { 112 executableTimeoutMs = DEFAULT_EXECUTABLE_TIMEOUT_MS; 113 } 114 115 // Provided timeout must be between 5s and 120s. 116 if (executableTimeoutMs < MINIMUM_EXECUTABLE_TIMEOUT_MS 117 || executableTimeoutMs > MAXIMUM_EXECUTABLE_TIMEOUT_MS) { 118 throw new IllegalArgumentException( 119 String.format( 120 "The executable timeout must be between %s and %s milliseconds.", 121 MINIMUM_EXECUTABLE_TIMEOUT_MS, MAXIMUM_EXECUTABLE_TIMEOUT_MS)); 122 } 123 124 executableCommand = (String) executable.get(COMMAND_KEY); 125 outputFilePath = (String) executable.get(OUTPUT_FILE_KEY); 126 } 127 getCommand()128 String getCommand() { 129 return executableCommand; 130 } 131 getTimeoutMs()132 int getTimeoutMs() { 133 return executableTimeoutMs; 134 } 135 136 @Nullable getOutputFilePath()137 String getOutputFilePath() { 138 return outputFilePath; 139 } 140 } 141