s4s-editor / local-scratch-vm /src /util /project-permissions.js
soiz1's picture
Upload 811 files
30c32c8 verified
raw
history blame
7.09 kB
class ProjectPermissionManager {
static permissions = {
javascript: true,
camera: true,
allWebsites: true,
cameraPictures: false,
scratchSignIn: true,
limitedWebsites: {}
};
static permissionMessages = {
javascript: "use custom code in JavaScript",
camera: "turn on your camera",
allWebsites: "display all websites",
cameraPictures: "take screenshots while your camera is on",
scratchSignIn: "sign in with Scratch",
limitedWebsite: "display $0",
};
static permissionDrawbacks = {
javascript: [],
camera: [],
allWebsites: [],
cameraPictures: ["take a screenshot while your camera is visible"],
scratchSignIn: [],
limitedWebsite: [],
};
static requiresCode = [];
static disabledPermissions = [];
static get skipPermissionRequest() {
if (!vm) return false;
if (!vm.runtime) return false;
return vm.runtime.isProjectPermissionManagerDisabled === true;
}
static GenerateCode(length) {
return Array.from(new Array(length).keys()).map(() => { return Math.round(Math.random() * 9) }).join(""); // generates something like 281964
}
static EditPromptForAcceptCheck(string) {
return String(string).replace(/ /gmi, "").toLowerCase();
}
static RequestPermission(name, ...args) {
// packager
if (ProjectPermissionManager.skipPermissionRequest === true) return true;
if (ProjectPermissionManager.disabledPermissions.includes(name)) return false;
if (name == "limitedWebsite") {
if (args.length < 1) throw new Error("No URL specified what are you trying to get permission for bro");
if (!ProjectPermissionManager.IsUrlSafe(args[0])) return false;
};
// check if we already gave permission to do this
if (name == "limitedWebsite") {
if (ProjectPermissionManager.permissions.limitedWebsites[args[0]] == true) return true;
};
if (ProjectPermissionManager.permissions[name]) return true;
let string = `Allow this project to ${ProjectPermissionManager.permissionMessages[name]}?`;
for (let i = 0; i < args.length; i++) {
const argument = args[i];
string = string.replace(`$${i}`, (ProjectPermissionManager.IsDataUrl(argument) ? "custom website" : String(argument)));
}
string += `\n\nThis will allow the project to:\n`;
ProjectPermissionManager.permissionDrawbacks[name].forEach(drawback => {
string += `▪ ${drawback}\n`;
});
let acceptCode = "ok";
if (ProjectPermissionManager.requiresCode.includes(name)) {
acceptCode = ProjectPermissionManager.GenerateCode(6);
};
string += `\nType "${acceptCode}" to allow, or type "stop" to never ask for this permission again.`;
const allow = ProjectPermissionManager.EditPromptForAcceptCheck(prompt(string, ""));
if (allow == "stop") {
// yes this does intentionally disable asking for any specific website after one was rejected
ProjectPermissionManager.disabledPermissions.push(name);
return false;
}
const allowed = allow === String(acceptCode);
if (name == "limitedWebsite") {
if (!allowed) return false;
ProjectPermissionManager.permissions.limitedWebsites[args[0]] = true;
return true;
};
ProjectPermissionManager.permissions[name] = allowed;
return allowed;
};
static RequestAllPermissions() {
// packager
if (ProjectPermissionManager.skipPermissionRequest === true) return true;
if (ProjectPermissionManager.disabledPermissions.includes("all")) return false;
const permissions = [];
Object.getOwnPropertyNames(ProjectPermissionManager.permissions).forEach(permissionName => {
if (typeof ProjectPermissionManager.permissions[permissionName] != "boolean") return;
permissions.push(permissionName);
});
let string = `Give all permissions to this project?`;
string += `\n\nThis will allow the project to:\n`;
permissions.forEach(permissionName => {
ProjectPermissionManager.permissionDrawbacks[permissionName].forEach(drawback => {
string += `▪ ${drawback}\n`;
});
});
const acceptCode = ProjectPermissionManager.GenerateCode(8);
string += `\nType "${acceptCode}" to allow, or type "stop" to never ask for all permissions again.`;
const allow = ProjectPermissionManager.EditPromptForAcceptCheck(prompt(string, ""));
if (allow == "stop") {
ProjectPermissionManager.disabledPermissions.push("all");
return false;
}
const allowed = allow === String(acceptCode);
if (!allowed) return false;
Object.getOwnPropertyNames(ProjectPermissionManager.permissions).forEach(permissionName => {
if (typeof ProjectPermissionManager.permissions[permissionName] != "boolean") return;
ProjectPermissionManager.permissions[permissionName] = true;
});
return true;
}
static CanCreateURLObject(url) {
let success = true;
try {
new URL(url);
} catch {
success = false;
}
return success;
}
static IsDataUrl(url) {
// if its not a valid url
// its probably not a data url
if (!this.CanCreateURLObject(url)) return false;
// create url object cuz ez to read
const urlObject = new URL(url);
// now we can just check the protocol
return urlObject.protocol === 'data:';
};
static IsUrlSafe(url) { // checks for non-kid friendly urls because that would be a big stinker
// we dont know what this is just say yup thas good
if (!this.CanCreateURLObject(url)) return false;
// custom urls cannot be checked yet, just say its safe for now
if (ProjectPermissionManager.IsDataUrl(url)) return true;
const urlObject = new URL(url);
const origin = urlObject.origin.toLowerCase();
// check origin for stuff
let returningValue = true;
// obviously this can be bypassed
// but it blocks large or well-known sites
// from working straight out of the gate
// projects with these sites will not be approved anyways
if (
origin.includes("xxx")
|| origin.includes("adult")
|| origin.includes(atob("c2V4"))
|| origin.includes(atob("cG9ybg=="))
|| origin.includes(atob("Ym9vcnU="))
|| origin.includes(atob("aGVudGFp"))
) returningValue = false;
// const mainName = link.match(/(?=(\.|\/\/))[^\n]+(?=(\.))/gmi)[0].replace(/(\/\/|)[^\n]+(?=(\.))/gmi, "").replace(/\/\//gmi, "")
return returningValue;
};
};
module.exports = ProjectPermissionManager;