001package org.intellimate.izou.security;
002
003import org.intellimate.izou.addon.AddOnModel;
004import org.intellimate.izou.main.Main;
005import org.intellimate.izou.security.exceptions.IzouPermissionException;
006
007import java.io.File;
008import java.io.FilePermission;
009import java.io.IOException;
010import java.net.URL;
011import java.security.Permission;
012import java.util.ArrayList;
013import java.util.List;
014
015/**
016 * The FilePermissionModule is used to check the access to files
017 * @author LeanderK
018 * @version 1.0
019 */
020public class FilePermissionModule extends PermissionModule {
021    private final List<String> forbiddenReadFiles;
022    private final List<File> allowedWriteDirectories;
023    private final List<File> forbiddenWriteDirectories;
024    private final List<String> forbiddenWriteFilesNames;
025
026    /**
027     * Creates a new PermissionModule
028     *
029     * @param main the instance of main
030     */
031    FilePermissionModule(Main main, SecurityManager securityManager) {
032        super(main, securityManager);
033        forbiddenReadFiles = new ArrayList<>();
034        forbiddenWriteDirectories = new ArrayList<>();
035        forbiddenWriteFilesNames = new ArrayList<>();
036        forbiddenWriteFilesNames.add("addon_config.properties");
037        allowedWriteDirectories = new ArrayList<>();
038        allowedWriteDirectories.add(main.getFileSystemManager().getResourceLocation());
039        allowedWriteDirectories.add(main.getFileSystemManager().getLogsLocation());
040        allowedWriteDirectories.add(main.getFileSystemManager().getPropertiesLocation());
041        allowedWriteDirectories.add(main.getFileSystemManager().getResourceLocation());
042        allowedWriteDirectories.add(main.getFileSystemManager().getLibLocation());
043        if (getMain().getFileSystemManager().getIzouJarLocation().isDirectory()) {
044            allowedWriteDirectories.add(new File(getMain().getFileSystemManager().getIzouJarLocation() + File.separator + "META-INF" + File.separator + "services"));
045        } else {
046            URL metaInfo = this.getClass().getClassLoader().getResource("META-INF/services");
047            if (metaInfo != null)
048                allowedWriteDirectories.add(new File(metaInfo.getFile()));
049        }
050
051        if (Boolean.getBoolean("debug")) {
052            allowedWriteDirectories.add(new File(System.getProperty("user.home") + File.separator + ".m2"));
053            allowedWriteDirectories.add(main.getFileSystemManager().getIzouJarLocation());
054            allowedWriteDirectories.add(new File(System.getProperty("java.home")));
055        }
056    }
057
058    /**
059     * returns true if able to check permissions
060     *
061     * @param permission the permission to check
062     * @return true if able to, false if not
063     */
064    @Override
065    public boolean canCheckPermission(Permission permission) {
066        return permission instanceof FilePermission;
067    }
068
069    /**
070     * Checks if the given addOn is allowed to access the requested service and registers them if not yet registered.
071     *
072     * @param permission the Permission to check
073     * @param addon      the identifiable to check
074     * @throws IzouPermissionException thrown if the addOn is not allowed to access its requested service
075     */
076    @Override
077    public void checkPermission(Permission permission, AddOnModel addon) throws IzouPermissionException {
078        String canonicalName = permission.getName().intern().toLowerCase();
079        String canonicalAction =  permission.getActions().intern().toLowerCase();
080
081        if (canonicalName.contains("all files") || canonicalAction.equals("execute")) {
082            throw getException(permission.getName());
083        }
084        if (canonicalAction.equals("read")) {
085            fileReadCheck(canonicalName);
086        }
087        fileWriteCheck(canonicalName, addon);
088    }
089
090    /**
091     * Determines if the file at the given file path is safe to read from in all aspects, if so returns true, else false
092     *
093     * @param filePath the path to the file to read from
094     */
095    void fileReadCheck(String filePath) {
096        File potentialFile = new File(filePath);
097        String canonicalPath;
098        try {
099            canonicalPath = potentialFile.getCanonicalPath();
100        } catch (IOException e) {
101            error("Error getting canonical path", e);
102            throw getException(filePath);
103        }
104
105        if (forbiddenReadFiles.stream().anyMatch(canonicalPath::startsWith)) {
106            throw getException(filePath);
107        }
108    }
109
110    /**
111     * Determines if the file at the given file path is safe to write to in all aspects, if so returns true, else false
112     *
113     * @param filePath the path to the file to write to
114     * @param addOnModel the AddonModel
115     */
116    void fileWriteCheck(String filePath, AddOnModel addOnModel) {
117        File request;
118        try {
119            request =  new File(filePath).getCanonicalFile();
120        } catch (IOException e) {
121            error("Error getting canonical path", e);
122            throw getException(filePath);
123        }
124
125        isForbidden(request, addOnModel);
126
127        boolean success = false;
128        if (allowedWriteDirectories.stream()
129                .anyMatch(compare -> request.toPath().startsWith(compare.toPath()))) {
130            success = true;
131        }
132
133        for (String name : forbiddenWriteFilesNames) {
134            if (request.getName().equals(name)) {
135                success = false;
136            }
137        }
138
139        if (!success) {
140            throw getException(filePath);
141        }
142
143        if (!getSecurityManager().getSecureAccess().checkForExistingFileOrDirectory(request.toString())
144                || getSecurityManager().getSecureAccess().checkForDirectory(request.toString())) {
145            return;
146        }
147    }
148
149    /**
150     * throws an Exception if the
151     * @param request the requested File
152     * @param addOnModel the AddonModel
153     */
154    private void isForbidden(File request, AddOnModel addOnModel) {
155        if (forbiddenWriteDirectories.stream()
156                .anyMatch(compare -> request.toPath().startsWith(compare.toPath()))) {
157            throw getException("file: " + request.toString() + " is forbidden. Attempt made by: "
158                    + addOnModel.getID());
159        }
160    }
161}