/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2023 Red Hat, Inc., and individual 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.jboss.installer.postinstall.task.utils;

import org.jboss.installer.core.LoggerUtils;

import java.io.FileNotFoundException;
import java.io.IOException;
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.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;

public class FilePermissionUtils {

    public static void setOwnerOnly(Path path) throws IOException, UnsupportedFileSystemException {
        Objects.requireNonNull(path);
        if (!Files.exists(path)) {
            throw new FileNotFoundException(path.toString());
        }

        final FileSystem fs = FileSystems.getDefault();
        final Set<String> supportedViews = fs.supportedFileAttributeViews();

        // make sure only owner can read/write it
        if (supportedViews.contains("posix")) {
            setUnixPermissions(path);
        } else if (supportedViews.contains("acl")) {
            setWindowsPermissions(path);
        } else {
            throw new UnsupportedFileSystemException();
        }
    }

    private static void setUnixPermissions(Path path) throws IOException {
        LoggerUtils.systemLog.tracef("Setting POSIX permissions for " + path);
        final Set<PosixFilePermission> perms = new HashSet<>();
        perms.add(PosixFilePermission.OWNER_READ);
        perms.add(PosixFilePermission.OWNER_WRITE);
        if (Files.isDirectory(path)) {
            LoggerUtils.systemLog.tracef("Adding EXECUTE for directory");
            perms.add(PosixFilePermission.OWNER_EXECUTE);
        }

        LoggerUtils.systemLog.tracef("Applying new POSIX permissions");
        Files.setPosixFilePermissions(path, perms);
    }

    private static void setWindowsPermissions(Path path) throws IOException {
        LoggerUtils.systemLog.tracef("Setting ACL permissions for " + path);
        final AclFileAttributeView aclView = Files.getFileAttributeView(path, AclFileAttributeView.class);
        final UserPrincipal owner = aclView.getOwner();

        final List<AclEntry> userRestrictedAcls = new ArrayList<>();
        for (AclEntry aclEntry : aclView.getAcl()) {
            if ("BUILTIN\\Administrators".equals(aclEntry.principal().getName())
                    || "NT AUTHORITY\\SYSTEM".equals(aclEntry.principal().getName())) {
                LoggerUtils.systemLog.tracef("Persisting permissions for " + aclEntry.principal().getName());
                userRestrictedAcls.add(aclEntry);
            }
        }

        LoggerUtils.systemLog.tracef("Adding ALLOW permissions for " + owner.getName());
        final 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();
        userRestrictedAcls.add(entry);

        LoggerUtils.systemLog.tracef("Applying new ACL permissions");
        aclView.setAcl(userRestrictedAcls);
    }

    public static class UnsupportedFileSystemException extends Exception {

    }
}
