/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.client.admin.cli.util;
import org.jboss.aesh.console.AeshConsoleBufferBuilder;
import org.jboss.aesh.console.AeshInputProcessorBuilder;
import org.jboss.aesh.console.ConsoleBuffer;
import org.jboss.aesh.console.InputProcessor;
import org.jboss.aesh.console.Prompt;
import org.jboss.aesh.console.command.invocation.CommandInvocation;
import org.keycloak.client.admin.cli.aesh.Globals;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.AclEntry;
import java.nio.file.attribute.AclEntryPermission;
import java.nio.file.attribute.AclEntryType;
import java.nio.file.attribute.AclFileAttributeView;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.UserPrincipal;
import java.util.Formatter;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import static java.nio.file.Files.createDirectories;
import static java.nio.file.Files.createFile;
import static java.nio.file.Files.isDirectory;
import static java.nio.file.Files.isRegularFile;
import static org.keycloak.client.admin.cli.util.OsUtil.OS_ARCH;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class IoUtil {
public static String readFileOrStdin(String file) {
String content;
if ("-".equals(file)) {
content = readFully(System.in);
} else {
try (InputStream is = new FileInputStream(file)) {
content = readFully(is);
} catch (FileNotFoundException e) {
throw new RuntimeException("File not found: " + file);
} catch (IOException e) {
throw new RuntimeException("Failed to read file: " + file, e);
}
}
return content;
}
public static void waitFor(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted");
}
}
public static String readSecret(String prompt, CommandInvocation invocation) {
// TODO Windows hack - masking not working on Windows
char maskChar = OS_ARCH.isWindows() ? 0 : '*';
ConsoleBuffer consoleBuffer = new AeshConsoleBufferBuilder()
.shell(invocation.getShell())
.prompt(new Prompt(prompt, maskChar))
.create();
InputProcessor inputProcessor = new AeshInputProcessorBuilder()
.consoleBuffer(consoleBuffer)
.create();
consoleBuffer.displayPrompt();
// activate stdin
Globals.stdin.setInputStream(System.in);
String result;
try {
do {
result = inputProcessor.parseOperation(invocation.getInput());
} while (result == null);
} catch (Exception e) {
throw new RuntimeException("^C", e);
}
/*
if (!Globals.stdin.isStdinAvailable()) {
try {
return readLine(new InputStreamReader(System.in));
} catch (IOException e) {
throw new RuntimeException("Standard input not available");
}
}
*/
// Windows hack - get rid of any \n
result = result.replaceAll("\\n", "");
return result;
}
public static String readFully(InputStream is) {
Charset charset = Charset.forName("utf-8");
StringBuilder out = new StringBuilder();
byte [] buf = new byte[8192];
int rc;
try {
while ((rc = is.read(buf)) != -1) {
out.append(new String(buf, 0, rc, charset));
}
} catch (Exception e) {
throw new RuntimeException("Failed to read stream", e);
}
return out.toString();
}
public static void copyStream(InputStream is, OutputStream os) {
byte [] buf = new byte[8192];
int rc;
try (InputStream input = is) {
while ((rc = input.read(buf)) != -1) {
os.write(buf, 0, rc);
}
} catch (Exception e) {
throw new RuntimeException("Failed to read/write a stream: ", e);
} finally {
try {
os.flush();
} catch (IOException e) {
throw new RuntimeException("Failed to write a stream: ", e);
}
}
}
public static void ensureFile(Path path) throws IOException {
FileSystem fs = FileSystems.getDefault();
Set<String> supportedViews = fs.supportedFileAttributeViews();
Path parent = path.getParent();
if (!isDirectory(parent)) {
createDirectories(parent);
// make sure only owner can read/write it
if (supportedViews.contains("posix")) {
setUnixPermissions(parent);
} else if (supportedViews.contains("acl")) {
setWindowsPermissions(parent);
} else {
warnErr("Failed to restrict access permissions on .keycloak directory: " + parent);
}
}
if (!isRegularFile(path)) {
createFile(path);
// make sure only owner can read/write it
if (FileSystems.getDefault().supportedFileAttributeViews().contains("posix")) {
setUnixPermissions(path);
} else if (supportedViews.contains("acl")) {
setWindowsPermissions(path);
} else {
warnErr("Failed to restrict access permissions on config file: " + path);
}
}
}
private static void setUnixPermissions(Path path) throws IOException {
Set<PosixFilePermission> perms = new HashSet<>();
perms.add(PosixFilePermission.OWNER_READ);
perms.add(PosixFilePermission.OWNER_WRITE);
if (isDirectory(path)) {
perms.add(PosixFilePermission.OWNER_EXECUTE);
}
Files.setPosixFilePermissions(path, perms);
}
private static void setWindowsPermissions(Path path) throws IOException {
AclFileAttributeView view = Files.getFileAttributeView(path, AclFileAttributeView.class);
UserPrincipal owner = view.getOwner();
List<AclEntry> acl = view.getAcl();
ListIterator<AclEntry> it = acl.listIterator();
while (it.hasNext()) {
AclEntry entry = it.next();
if ("BUILTIN\\Administrators".equals(entry.principal().getName()) || "NT AUTHORITY\\SYSTEM".equals(entry.principal().getName())) {
continue;
}
it.remove();
}
AclEntry entry = AclEntry.newBuilder()
.setType(AclEntryType.ALLOW)
.setPrincipal(owner)
.setPermissions(AclEntryPermission.READ_DATA, AclEntryPermission.WRITE_DATA,
AclEntryPermission.APPEND_DATA, AclEntryPermission.READ_NAMED_ATTRS,
AclEntryPermission.WRITE_NAMED_ATTRS, AclEntryPermission.EXECUTE,
AclEntryPermission.READ_ATTRIBUTES, AclEntryPermission.WRITE_ATTRIBUTES,
AclEntryPermission.DELETE, AclEntryPermission.READ_ACL, AclEntryPermission.SYNCHRONIZE)
.build();
acl.add(entry);
view.setAcl(acl);
}
public static void printOut(String msg) {
System.out.println(msg);
}
public static void printErr(String msg) {
System.err.println(msg);
}
public static void printfOut(String format, String ... params) {
System.out.println(new Formatter().format("WARN: " + format, params));
}
public static void warnOut(String msg) {
System.out.println("WARN: " + msg);
}
public static void warnErr(String msg) {
System.err.println("WARN: " + msg);
}
public static void warnfOut(String format, String ... params) {
System.out.println(new Formatter().format("WARN: " + format, params));
}
public static void warnfErr(String format, String ... params) {
System.err.println(new Formatter().format("WARN: " + format, params));
}
public static void logOut(String msg) {
System.out.println("LOG: " + msg);
}
}