forked from tools/josh
parent
50fc0ffbd5
commit
616df7beba
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "fwdekker.com",
|
||||
"version": "0.30.1",
|
||||
"version": "0.31.0",
|
||||
"description": "The source code of [my personal website](https://fwdekker.com/).",
|
||||
"author": "Felix W. Dekker",
|
||||
"repository": {
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
--><span id="terminalInputPrefix"></span><!--
|
||||
--><span id="terminalInputField" contenteditable="true" autocapitalize="none"
|
||||
spellcheck="false"></span><!--
|
||||
--></span><!--
|
||||
--></span><!--
|
||||
--><span id="terminalSuggestions"></span><!--
|
||||
--></div>
|
||||
</main>
|
||||
|
|
|
@ -525,6 +525,8 @@ export const commandBinaries: { [key: string]: string } = {
|
|||
|
||||
<u>/dev</u> Contains special files and device files that refer to physical devices.
|
||||
|
||||
<u>/etc</u> System configuration files and scripts.
|
||||
|
||||
<u>/home</u> Contains directories for users to store personal files in.
|
||||
|
||||
<u>/root</u> The home directory of the root user.\`.trimMultiLines()
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import {commandBinaries} from "./Commands";
|
||||
import {emptyFunction, getFileExtension, IllegalArgumentError} from "./Shared";
|
||||
import {Stream} from "./Stream";
|
||||
import {User} from "./UserList";
|
||||
|
||||
|
||||
/**
|
||||
|
@ -30,6 +31,14 @@ export class FileSystem {
|
|||
"dev": new Directory({
|
||||
"null": new NullFile()
|
||||
}),
|
||||
"etc": new Directory({
|
||||
"passwd": new File(
|
||||
[
|
||||
new User("root", "root", "/root", ""),
|
||||
new User("felix", "felix", undefined, "")
|
||||
].map(it => User.toString(it)).join("\n") + "\n"
|
||||
)
|
||||
}),
|
||||
"home": new Directory({
|
||||
"felix": new Directory({
|
||||
"personal": new Directory({
|
||||
|
|
|
@ -50,8 +50,8 @@ export class Shell {
|
|||
*/
|
||||
constructor(inputHistory: InputHistory) {
|
||||
this.inputHistory = inputHistory;
|
||||
this.userList = new UserList();
|
||||
this.fileSystem = Persistence.getFileSystem();
|
||||
this.userList = new UserList(this.fileSystem);
|
||||
this.environment = Persistence.getEnvironment(this.userList);
|
||||
this.commands = new Commands(this.environment, this.userList, this.fileSystem);
|
||||
|
||||
|
|
|
@ -1,44 +1,94 @@
|
|||
/**
|
||||
* Manages a list of users.
|
||||
*/
|
||||
import {File, FileSystem, Path} from "./FileSystem";
|
||||
|
||||
|
||||
/**
|
||||
* Manages a file containing user data.
|
||||
*/
|
||||
export class UserList {
|
||||
/**
|
||||
* All users that exist in the system.
|
||||
* The default contents of the user data file, inserted if the file is unexpectedly removed or invalidated.
|
||||
*
|
||||
* This is a function to prevent accidental modification of these data.
|
||||
*/
|
||||
private _users: User[];
|
||||
private readonly GET_DEFAULT_USER = () => new User("root", "root", "/root", "The root user.");
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new list of users.
|
||||
*
|
||||
* @param users the list of users that are available, or `undefined` if the default users should be available
|
||||
* The file system in which the user data file is located.
|
||||
*/
|
||||
constructor(users: User[] | undefined = undefined) {
|
||||
if (users === undefined)
|
||||
this._users = [
|
||||
new User("felix", "password", "/home/felix", "Why are you logged in on <i>my</i> account?"),
|
||||
new User("root", "g9PjKu", "/root", "You're a hacker, Harry!")
|
||||
];
|
||||
else
|
||||
this._users = users;
|
||||
private readonly fileSystem: FileSystem;
|
||||
/**
|
||||
* The path to the file containing user data.
|
||||
*/
|
||||
private readonly userFilePath: Path;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new user list manager.
|
||||
*
|
||||
* @param fileSystem the file system in which the user data file is located
|
||||
* @param userFilePath the path to the file containing user data
|
||||
*/
|
||||
constructor(fileSystem: FileSystem, userFilePath: Path = new Path("/etc/passwd")) {
|
||||
this.fileSystem = fileSystem;
|
||||
this.userFilePath = userFilePath;
|
||||
|
||||
this.userFile; // Initialize file
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the user data file, creating it if it does not exist.
|
||||
*
|
||||
* @return the user data file, creating it if it does not exist
|
||||
*/
|
||||
private get userFile(): File {
|
||||
let userFile = this.fileSystem.get(this.userFilePath);
|
||||
if (userFile === undefined) {
|
||||
userFile = new File(User.toString(this.GET_DEFAULT_USER()) + "\n");
|
||||
this.fileSystem.add(this.userFilePath, userFile, true);
|
||||
} else if (!(userFile instanceof File)) {
|
||||
userFile = new File(User.toString(this.GET_DEFAULT_USER()) + "\n");
|
||||
this.fileSystem.remove(this.userFilePath);
|
||||
this.fileSystem.add(this.userFilePath, userFile, true);
|
||||
}
|
||||
return userFile as File;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of the list of all users.
|
||||
*/
|
||||
get users(): User[] {
|
||||
return this._users.slice();
|
||||
return this.userFile.open("read").read().split("\n").map(it => User.fromString(it));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds the given user to the user list.
|
||||
*
|
||||
* If the user already exists, nothing happens.
|
||||
*
|
||||
* @param user the user to add
|
||||
* @return `true` if and only if the user was added
|
||||
*/
|
||||
add(user: User) {
|
||||
this._users.push(user);
|
||||
add(user: User): boolean {
|
||||
if (this.has(user.name))
|
||||
return false;
|
||||
|
||||
this.userFile.open("append").writeLine(User.toString(user));
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the user with the given name, or `undefined` if there is no such user.
|
||||
*
|
||||
* @param name the name of the user to return
|
||||
*/
|
||||
get(name: string): User | undefined {
|
||||
return this.users.find(it => it.name === name);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -49,15 +99,6 @@ export class UserList {
|
|||
has(name: string): boolean {
|
||||
return this.get(name) !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the user with the given name, or `undefined` if there is no such user.
|
||||
*
|
||||
* @param name the name of the user to return
|
||||
*/
|
||||
get(name: string): User | undefined {
|
||||
return this._users.find(it => it.name === name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -88,13 +129,35 @@ export class User {
|
|||
*
|
||||
* @param name the name of the user
|
||||
* @param password the password of the user
|
||||
* @param home the path to the user's home directory
|
||||
* @param home the path to the user's home directory, or `undefined` to use `/home/<name>`
|
||||
* @param description the description of the user
|
||||
*/
|
||||
constructor(name: string, password: string, home: string, description: string) {
|
||||
constructor(name: string, password: string, home: string | undefined, description: string = "") {
|
||||
this.name = name;
|
||||
this.password = password;
|
||||
this.home = home;
|
||||
this.home = home ?? `/home/${name}`;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts a string to a user object.
|
||||
*
|
||||
* @param string the string to convert to a user object
|
||||
* @return the user object described by the given string
|
||||
*/
|
||||
static fromString(string: string): User {
|
||||
const parts = string.split("|");
|
||||
return new User(parts[0], parts[1], parts[2], parts[3]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a user object to a string.
|
||||
*
|
||||
* @param user the user to convert to a string
|
||||
* @return the string describing the given user
|
||||
*/
|
||||
static toString(user: User): string {
|
||||
return `${user.name}|${user.password}|${user.home}|${user.description}`;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
import "mocha";
|
||||
import {expect} from "chai";
|
||||
|
||||
import {Directory, File, FileSystem, Path} from "../main/js/FileSystem";
|
||||
import {User, UserList} from "../main/js/UserList";
|
||||
|
||||
|
||||
describe("user list", () => {
|
||||
let fileSystem: FileSystem;
|
||||
let userList: UserList;
|
||||
let initialContents: string;
|
||||
|
||||
|
||||
const readUserFile = () => (fileSystem.get(new Path("/etc/passwd")) as File).open("read").read();
|
||||
|
||||
|
||||
beforeEach(() => {
|
||||
fileSystem = new FileSystem(new Directory());
|
||||
userList = new UserList(fileSystem);
|
||||
initialContents = readUserFile();
|
||||
});
|
||||
|
||||
|
||||
describe("file management", () => {
|
||||
it("populates the file with a default root account if the file disappeared", () => {
|
||||
fileSystem.remove(new Path("/etc/passwd"));
|
||||
|
||||
expect(userList.has("root")).to.be.true;
|
||||
expect(readUserFile()).to.equal(initialContents);
|
||||
});
|
||||
|
||||
it("populates the file with a default root account if the target is a directory", () => {
|
||||
fileSystem.remove(new Path("/etc/passwd"));
|
||||
fileSystem.add(new Path("/etc/passwd"), new Directory(), true);
|
||||
|
||||
expect(userList.has("root")).to.be.true;
|
||||
expect(readUserFile()).to.equal(initialContents);
|
||||
});
|
||||
});
|
||||
|
||||
describe("add", () => {
|
||||
it("adds the given user", () => {
|
||||
const user = new User("user", "pwd", "/home", "");
|
||||
|
||||
userList.add(user);
|
||||
|
||||
expect(readUserFile()).to.equal(initialContents + User.toString(user) + "\n");
|
||||
});
|
||||
|
||||
it("does not add duplicate users", () => {
|
||||
const user = new User("user", "pwd", "/home", "");
|
||||
|
||||
userList.add(user);
|
||||
userList.add(user);
|
||||
|
||||
expect(readUserFile()).to.equal(initialContents + User.toString(user) + "\n");
|
||||
});
|
||||
});
|
||||
|
||||
describe("get", () => {
|
||||
it("returns the indicated user if it exists", () => {
|
||||
const user = new User("user", "pwd", "/home", "");
|
||||
|
||||
userList.add(user);
|
||||
|
||||
expect(userList.get("user")).to.deep.equal(user);
|
||||
});
|
||||
|
||||
it("returns undefined if the user does not exist", () => {
|
||||
expect(userList.get("user")).to.be.undefined;
|
||||
});
|
||||
});
|
||||
|
||||
describe("has", () => {
|
||||
it("returns `true` if the user exists", () => {
|
||||
userList.add(new User("user", "pwd", "/home", ""));
|
||||
|
||||
expect(userList.has("user")).to.be.true;
|
||||
});
|
||||
|
||||
it("returns `false` if the user does not exist", () => {
|
||||
expect(userList.has("user")).to.be.false;
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue