From 5e7325aa453bbf01cd61013700ba97bcfe5eece5 Mon Sep 17 00:00:00 2001 From: Adrien Castex Date: Sat, 20 May 2017 21:54:51 +0200 Subject: [PATCH] Implemented User management + HTTP authentication + the structure of the privilege management + some privilege/lock checkers --- lib/resource/IResource.d.ts | 7 +- lib/resource/std/StandardResource.d.ts | 7 +- lib/resource/std/StandardResource.js | 46 +-- lib/server/MethodCallArgs.d.ts | 10 +- lib/server/MethodCallArgs.js | 52 +++- lib/server/WebDAVServer.d.ts | 11 +- lib/server/WebDAVServer.js | 75 +++-- lib/server/WebDAVServerOptions.d.ts | 11 + lib/server/WebDAVServerOptions.js | 20 ++ lib/server/commands/Commands.js | 4 + lib/server/commands/Copy.js | 222 +++++++------- lib/server/commands/Delete.js | 14 +- lib/server/commands/Put.js | 60 ++-- lib/user/IUser.d.ts | 6 + lib/user/{User.js => IUser.js} | 0 lib/user/IUserManager.d.ts | 6 + lib/user/IUserManager.js | 2 + lib/user/User.d.ts | 2 - .../authentication/HTTPAuthentication.d.ts | 8 + lib/user/authentication/HTTPAuthentication.js | 2 + .../HTTPBasicAuthentication.d.ts | 12 + .../authentication/HTTPBasicAuthentication.js | 48 +++ .../HTTPDigestAuthentication.d.ts | 14 + .../HTTPDigestAuthentication.js | 70 +++++ lib/user/privilege/FakePrivilegeManager.d.ts | 17 ++ lib/user/privilege/FakePrivilegeManager.js | 35 +++ lib/user/privilege/IPrivilegeManager.d.ts | 34 +++ lib/user/privilege/IPrivilegeManager.js | 22 ++ .../privilege/SimplePrivilegeManager.d.ts | 29 ++ lib/user/privilege/SimplePrivilegeManager.js | 29 ++ lib/user/simple/SimpleUser.d.ts | 8 + lib/user/simple/SimpleUser.js | 12 + lib/user/simple/SimpleUserManager.d.ts | 10 + lib/user/simple/SimpleUserManager.js | 31 ++ src/resource/IResource.ts | 8 +- src/resource/std/StandardResource.ts | 25 +- src/server/MethodCallArgs.ts | 75 ++++- src/server/WebDAVServer.ts | 102 ++++--- src/server/WebDAVServerOptions.ts | 28 ++ src/server/commands/Commands.ts | 4 + src/server/commands/Copy.ts | 282 +++++++++--------- src/server/commands/Delete.ts | 14 +- src/server/commands/Put.ts | 69 +++-- src/user/IUser.ts | 8 + src/user/IUserManager.ts | 8 + src/user/User.ts | 3 - src/user/authentication/HTTPAuthentication.ts | 11 + .../authentication/HTTPBasicAuthentication.ts | 63 ++++ .../HTTPDigestAuthentication.ts | 90 ++++++ src/user/privilege/FakePrivilegeManager.ts | 26 ++ src/user/privilege/IPrivilegeManager.ts | 85 ++++++ src/user/privilege/SimplePrivilegeManager.ts | 41 +++ src/user/simple/SimpleUser.ts | 11 + src/user/simple/SimpleUserManager.ts | 42 +++ 54 files changed, 1482 insertions(+), 449 deletions(-) create mode 100644 lib/user/IUser.d.ts rename lib/user/{User.js => IUser.js} (100%) create mode 100644 lib/user/IUserManager.d.ts create mode 100644 lib/user/IUserManager.js delete mode 100644 lib/user/User.d.ts create mode 100644 lib/user/authentication/HTTPAuthentication.d.ts create mode 100644 lib/user/authentication/HTTPAuthentication.js create mode 100644 lib/user/authentication/HTTPBasicAuthentication.d.ts create mode 100644 lib/user/authentication/HTTPBasicAuthentication.js create mode 100644 lib/user/authentication/HTTPDigestAuthentication.d.ts create mode 100644 lib/user/authentication/HTTPDigestAuthentication.js create mode 100644 lib/user/privilege/FakePrivilegeManager.d.ts create mode 100644 lib/user/privilege/FakePrivilegeManager.js create mode 100644 lib/user/privilege/IPrivilegeManager.d.ts create mode 100644 lib/user/privilege/IPrivilegeManager.js create mode 100644 lib/user/privilege/SimplePrivilegeManager.d.ts create mode 100644 lib/user/privilege/SimplePrivilegeManager.js create mode 100644 lib/user/simple/SimpleUser.d.ts create mode 100644 lib/user/simple/SimpleUser.js create mode 100644 lib/user/simple/SimpleUserManager.d.ts create mode 100644 lib/user/simple/SimpleUserManager.js create mode 100644 src/user/IUser.ts create mode 100644 src/user/IUserManager.ts delete mode 100644 src/user/User.ts create mode 100644 src/user/authentication/HTTPAuthentication.ts create mode 100644 src/user/authentication/HTTPBasicAuthentication.ts create mode 100644 src/user/authentication/HTTPDigestAuthentication.ts create mode 100644 src/user/privilege/FakePrivilegeManager.ts create mode 100644 src/user/privilege/IPrivilegeManager.ts create mode 100644 src/user/privilege/SimplePrivilegeManager.ts create mode 100644 src/user/simple/SimpleUser.ts create mode 100644 src/user/simple/SimpleUserManager.ts diff --git a/lib/resource/IResource.d.ts b/lib/resource/IResource.d.ts index 1553429d..a0c41d46 100644 --- a/lib/resource/IResource.d.ts +++ b/lib/resource/IResource.d.ts @@ -32,12 +32,13 @@ export interface IResource { read(callback: ReturnCallback): any; mimeType(callback: ReturnCallback): any; size(callback: ReturnCallback): any; - getLocks(lockKind: LockKind, callback: ReturnCallback): any; + getLocks(callback: ReturnCallback): any; setLock(lock: Lock, callback: SimpleCallback): any; - removeLock(uuid: string, owner: string, callback: ReturnCallback): any; + removeLock(uuid: string, callback: ReturnCallback): any; canLock(lockKind: LockKind, callback: ReturnCallback): any; getAvailableLocks(callback: ReturnCallback): any; - canRemoveLock(uuid: string, owner: string, callback: ReturnCallback): any; + canRemoveLock(uuid: string, callback: ReturnCallback): any; + getLock(uuid: string, callback: ReturnCallback): any; addChild(resource: IResource, callback: SimpleCallback): any; removeChild(resource: IResource, callback: SimpleCallback): any; getChildren(callback: ReturnCallback): any; diff --git a/lib/resource/std/StandardResource.d.ts b/lib/resource/std/StandardResource.d.ts index 242a9107..6e342d44 100644 --- a/lib/resource/std/StandardResource.d.ts +++ b/lib/resource/std/StandardResource.d.ts @@ -15,11 +15,12 @@ export declare abstract class StandardResource implements IResource { isSame(resource: IResource, callback: ReturnCallback): void; isOnTheSameFSWith(resource: IResource, callback: ReturnCallback): void; getAvailableLocks(callback: ReturnCallback): void; - getLocks(lockKind: LockKind, callback: ReturnCallback): void; + getLocks(callback: ReturnCallback): void; setLock(lock: Lock, callback: SimpleCallback): void; - removeLock(uuid: string, owner: string, callback: ReturnCallback): void; - canRemoveLock(uuid: string, owner: string, callback: ReturnCallback): void; + removeLock(uuid: string, callback: ReturnCallback): void; + canRemoveLock(uuid: string, callback: ReturnCallback): void; canLock(lockKind: LockKind, callback: ReturnCallback): void; + getLock(uuid: string, callback: ReturnCallback): void; setProperty(name: string, value: ResourcePropertyValue, callback: SimpleCallback): void; getProperty(name: string, callback: ReturnCallback): void; removeProperty(name: string, callback: SimpleCallback): void; diff --git a/lib/resource/std/StandardResource.js b/lib/resource/std/StandardResource.js index c27fc8a7..1a69695d 100644 --- a/lib/resource/std/StandardResource.js +++ b/lib/resource/std/StandardResource.js @@ -42,50 +42,28 @@ var StandardResource = (function () { new LockKind_1.LockKind(LockScope_1.LockScope.Shared, LockType_1.LockType.Write) ]); }; - StandardResource.prototype.getLocks = function (lockKind, callback) { - callback(null, this.lockBag.getLocks(lockKind)); + StandardResource.prototype.getLocks = function (callback) { + callback(null, this.lockBag.getLocks()); }; StandardResource.prototype.setLock = function (lock, callback) { var locked = this.lockBag.setLock(lock); + this.updateLastModified(); callback(locked ? null : new Error('Can\'t lock the resource.')); }; - StandardResource.prototype.removeLock = function (uuid, owner, callback) { - this.getChildren(function (e, children) { - if (e) { - callback(e, false); - return; - } - var nb = children.length + 1; - children.forEach(function (child) { - child.canRemoveLock(uuid, owner, go); - }); - go(null, true); - function go(e, can) { - if (e) { - nb = -1; - callback(e, false); - return; - } - if (!can) { - nb = -1; - callback(null, false); - return; - } - --nb; - if (nb === 0) { - this.lockBag.removeLock(uuid, owner); - this.updateLastModified(); - callback(null, true); - } - } - }); + StandardResource.prototype.removeLock = function (uuid, callback) { + this.lockBag.removeLock(uuid); + this.updateLastModified(); + callback(null, true); }; - StandardResource.prototype.canRemoveLock = function (uuid, owner, callback) { - callback(null, this.lockBag.canRemoveLock(uuid, owner)); + StandardResource.prototype.canRemoveLock = function (uuid, callback) { + callback(null, this.lockBag.canRemoveLock(uuid)); }; StandardResource.prototype.canLock = function (lockKind, callback) { callback(null, this.lockBag.canLock(lockKind)); }; + StandardResource.prototype.getLock = function (uuid, callback) { + callback(null, this.lockBag.getLock(uuid)); + }; StandardResource.prototype.setProperty = function (name, value, callback) { this.properties[name] = value; this.updateLastModified(); diff --git a/lib/server/MethodCallArgs.d.ts b/lib/server/MethodCallArgs.d.ts index 4863f27e..d27fba83 100644 --- a/lib/server/MethodCallArgs.d.ts +++ b/lib/server/MethodCallArgs.d.ts @@ -1,13 +1,16 @@ /// +import { BasicPrivilege } from '../user/privilege/IPrivilegeManager'; import { IResource, ReturnCallback } from '../resource/Resource'; import { XMLElement } from '../helper/XML'; import { WebDAVServer } from '../server/WebDAVServer'; import { FSPath } from '../manager/FSManager'; +import { IUser } from '../user/IUser'; import * as http from 'http'; export declare class MethodCallArgs { server: WebDAVServer; request: http.IncomingMessage; response: http.ServerResponse; + exit: () => void; callback: () => void; contentLength: number; depth: number; @@ -15,7 +18,12 @@ export declare class MethodCallArgs { path: FSPath; uri: string; data: string; - constructor(server: WebDAVServer, request: http.IncomingMessage, response: http.ServerResponse, callback: () => void); + user: IUser; + protected constructor(server: WebDAVServer, request: http.IncomingMessage, response: http.ServerResponse, exit: () => void, callback: () => void); + static create(server: WebDAVServer, request: http.IncomingMessage, response: http.ServerResponse, callback: (error: Error, mca: MethodCallArgs) => void): void; + requireCustomPrivilege(privileges: string | string[], resource: IResource, callback: () => void): void; + requirePrivilege(privileges: BasicPrivilege | BasicPrivilege[], resource: IResource, callback: () => void): void; + askForAuthentication(checkForUser: boolean, callback: (error: Error) => void): void; accept(regex: RegExp[]): number; findHeader(name: string, defaultValue?: string): string; getResource(callback: ReturnCallback): void; diff --git a/lib/server/MethodCallArgs.js b/lib/server/MethodCallArgs.js index 25e3dbed..d8fafe95 100644 --- a/lib/server/MethodCallArgs.js +++ b/lib/server/MethodCallArgs.js @@ -1,14 +1,18 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); +var IPrivilegeManager_1 = require("../user/privilege/IPrivilegeManager"); var XML_1 = require("../helper/XML"); +var HTTPCodes_1 = require("./HTTPCodes"); var FSManager_1 = require("../manager/FSManager"); +var Errors_1 = require("../Errors"); var http = require("http"); var url = require("url"); var MethodCallArgs = (function () { - function MethodCallArgs(server, request, response, callback) { + function MethodCallArgs(server, request, response, exit, callback) { this.server = server; this.request = request; this.response = response; + this.exit = exit; this.callback = callback; this.contentLength = parseInt(this.findHeader('Content-length', '0'), 10); this.depth = parseInt(this.findHeader('Depth', '0'), 10); @@ -16,6 +20,52 @@ var MethodCallArgs = (function () { this.uri = url.parse(request.url).pathname; this.path = new FSManager_1.FSPath(this.uri); } + MethodCallArgs.create = function (server, request, response, callback) { + var mca = new MethodCallArgs(server, request, response, null, null); + mca.askForAuthentication(false, function (e) { + if (e) { + callback(e, mca); + return; + } + server.httpAuthentication.getUser(mca, server.userManager, function (e, user) { + mca.user = user; + if (e) { + if (e === Errors_1.Errors.MissingAuthorisationHeader) + e = null; + } + callback(e, mca); + }); + }); + }; + MethodCallArgs.prototype.requireCustomPrivilege = function (privileges, resource, callback) { + var _this = this; + IPrivilegeManager_1.requirePrivilege(privileges, this, resource, function (e, can) { + if (e) { + _this.setCode(HTTPCodes_1.HTTPCodes.InternalServerError); + _this.exit(); + return; + } + if (!can) { + _this.setCode(HTTPCodes_1.HTTPCodes.Unauthorized); + _this.exit(); + return; + } + callback(); + }); + }; + MethodCallArgs.prototype.requirePrivilege = function (privileges, resource, callback) { + this.requireCustomPrivilege(privileges, resource, callback); + }; + MethodCallArgs.prototype.askForAuthentication = function (checkForUser, callback) { + if (checkForUser && this.user !== null && !this.user.isDefaultUser) { + callback(new Error('Already authenticated')); + return; + } + var auth = this.server.httpAuthentication.askForAuthentication(); + for (var name_1 in auth) + this.response.setHeader(name_1, auth[name_1]); + callback(null); + }; MethodCallArgs.prototype.accept = function (regex) { var accepts = this.findHeader('Accept', 'text/xml').split(','); for (var _i = 0, accepts_1 = accepts; _i < accepts_1.length; _i++) { diff --git a/lib/server/WebDAVServer.d.ts b/lib/server/WebDAVServer.d.ts index cb5fb6ac..5650485c 100644 --- a/lib/server/WebDAVServer.d.ts +++ b/lib/server/WebDAVServer.d.ts @@ -1,17 +1,23 @@ /// +import { WebDAVServerOptions } from './WebDAVServerOptions'; import { MethodCallArgs, WebDAVRequest } from './WebDAVRequest'; import { IResource, ReturnCallback } from '../resource/Resource'; -import { WebDAVServerOptions } from './WebDAVServerOptions'; +import { HTTPAuthentication } from '../user/authentication/HTTPAuthentication'; +import { IPrivilegeManager } from '../user/privilege/IPrivilegeManager'; import { FSPath } from '../manager/FSManager'; +import { IUserManager } from '../user/IUserManager'; import * as http from 'http'; export { WebDAVServerOptions } from './WebDAVServerOptions'; export declare class WebDAVServer { + httpAuthentication: HTTPAuthentication; + privilegeManager: IPrivilegeManager; rootResource: IResource; + userManager: IUserManager; + options: WebDAVServerOptions; methods: object; protected beforeManagers: WebDAVRequest[]; protected afterManagers: WebDAVRequest[]; protected unknownMethod: WebDAVRequest; - protected options: WebDAVServerOptions; protected server: http.Server; constructor(options?: WebDAVServerOptions); getResourceFromPath(path: FSPath | string[] | string, callback: ReturnCallback): any; @@ -22,7 +28,6 @@ export declare class WebDAVServer { method(name: string, manager: WebDAVRequest): void; beforeRequest(manager: WebDAVRequest): void; afterRequest(manager: WebDAVRequest): void; - protected createMethodCallArgs(req: http.IncomingMessage, res: http.ServerResponse): MethodCallArgs; protected normalizeMethodName(method: string): string; protected invokeBARequest(collection: WebDAVRequest[], base: MethodCallArgs, callback: any): void; protected invokeBeforeRequest(base: MethodCallArgs, callback: any): void; diff --git a/lib/server/WebDAVServer.js b/lib/server/WebDAVServer.js index 8754f33d..842ff1cb 100644 --- a/lib/server/WebDAVServer.js +++ b/lib/server/WebDAVServer.js @@ -1,19 +1,23 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); +var WebDAVServerOptions_1 = require("./WebDAVServerOptions"); var WebDAVRequest_1 = require("./WebDAVRequest"); -var Resource_1 = require("../resource/Resource"); var FSManager_1 = require("../manager/FSManager"); +var Errors_1 = require("../Errors"); var Commands_1 = require("./commands/Commands"); var http = require("http"); -var WebDAVServerOptions_1 = require("./WebDAVServerOptions"); -exports.WebDAVServerOptions = WebDAVServerOptions_1.WebDAVServerOptions; +var WebDAVServerOptions_2 = require("./WebDAVServerOptions"); +exports.WebDAVServerOptions = WebDAVServerOptions_2.WebDAVServerOptions; var WebDAVServer = (function () { function WebDAVServer(options) { this.beforeManagers = []; - this.rootResource = new Resource_1.RootResource(); this.afterManagers = []; this.methods = {}; - this.options = options; + this.options = WebDAVServerOptions_1.setDefaultServerOptions(options); + this.httpAuthentication = this.options.httpAuthentication; + this.privilegeManager = this.options.privilegeManager; + this.rootResource = this.options.rootResource; + this.userManager = this.options.userManager; for (var k in Commands_1.default) if (k === 'NotImplemented') this.onUnknownMethod(Commands_1.default[k]); @@ -78,32 +82,42 @@ var WebDAVServer = (function () { var method = _this.methods[_this.normalizeMethodName(req.method)]; if (!method) method = _this.unknownMethod; - var base = _this.createMethodCallArgs(req, res); - if (!method.chunked) { - var data_1 = ''; - var go_1 = function () { - base.data = data_1; - _this.invokeBeforeRequest(base, function () { - method(base, function () { - res.end(); - _this.invokeAfterRequest(base, null); - }); - }); - }; - if (base.contentLength === 0) { - go_1(); + WebDAVRequest_1.MethodCallArgs.create(_this, req, res, function (e, base) { + if (e) { + if (e === Errors_1.Errors.AuenticationPropertyMissing) + base.setCode(WebDAVRequest_1.HTTPCodes.Unauthorized); + else + base.setCode(WebDAVRequest_1.HTTPCodes.InternalServerError); + res.end(); + return; } - else { - req.on('data', function (chunk) { - data_1 += chunk.toString(); - if (data_1.length >= base.contentLength) { - if (data_1.length > base.contentLength) - data_1 = data_1.substring(0, base.contentLength); - go_1(); - } - }); + if (!method.chunked) { + var data_1 = ''; + var go_1 = function () { + base.data = data_1; + _this.invokeBeforeRequest(base, function () { + base.exit = function () { + res.end(); + _this.invokeAfterRequest(base, null); + }; + method(base, base.exit); + }); + }; + if (base.contentLength === 0) { + go_1(); + } + else { + req.on('data', function (chunk) { + data_1 += chunk.toString(); + if (data_1.length >= base.contentLength) { + if (data_1.length > base.contentLength) + data_1 = data_1.substring(0, base.contentLength); + go_1(); + } + }); + } } - } + }); }); this.server.listen(port); }; @@ -124,9 +138,6 @@ var WebDAVServer = (function () { WebDAVServer.prototype.afterRequest = function (manager) { this.afterManagers.push(manager); }; - WebDAVServer.prototype.createMethodCallArgs = function (req, res) { - return new WebDAVRequest_1.MethodCallArgs(this, req, res, null); - }; WebDAVServer.prototype.normalizeMethodName = function (method) { return method.toLowerCase(); }; diff --git a/lib/server/WebDAVServerOptions.d.ts b/lib/server/WebDAVServerOptions.d.ts index 34f86105..f95e2317 100644 --- a/lib/server/WebDAVServerOptions.d.ts +++ b/lib/server/WebDAVServerOptions.d.ts @@ -1,4 +1,15 @@ +import { HTTPAuthentication } from '../user/authentication/HTTPAuthentication'; +import { IPrivilegeManager } from '../user/privilege/IPrivilegeManager'; +import { IUserManager } from '../user/IUserManager'; +import { IResource } from '../resource/IResource'; export declare class WebDAVServerOptions { + requireAuthentification?: boolean; + httpAuthentication?: HTTPAuthentication; + privilegeManager?: IPrivilegeManager; + rootResource?: IResource; + userManager?: IUserManager; + lockTimeout?: number; port?: number; } export default WebDAVServerOptions; +export declare function setDefaultServerOptions(options: WebDAVServerOptions): WebDAVServerOptions; diff --git a/lib/server/WebDAVServerOptions.js b/lib/server/WebDAVServerOptions.js index 51e402a1..0947b80c 100644 --- a/lib/server/WebDAVServerOptions.js +++ b/lib/server/WebDAVServerOptions.js @@ -1,10 +1,30 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); +var HTTPBasicAuthentication_1 = require("../user/authentication/HTTPBasicAuthentication"); +var FakePrivilegeManager_1 = require("../user/privilege/FakePrivilegeManager"); +var SimpleUserManager_1 = require("../user/simple/SimpleUserManager"); +var RootResource_1 = require("../resource/std/RootResource"); var WebDAVServerOptions = (function () { function WebDAVServerOptions() { + this.requireAuthentification = false; + this.httpAuthentication = new HTTPBasicAuthentication_1.HTTPBasicAuthentication('default realm'); + this.privilegeManager = new FakePrivilegeManager_1.FakePrivilegeManager(); + this.rootResource = new RootResource_1.RootResource(); + this.userManager = new SimpleUserManager_1.SimpleUserManager(); + this.lockTimeout = 3600; this.port = 1900; } return WebDAVServerOptions; }()); exports.WebDAVServerOptions = WebDAVServerOptions; exports.default = WebDAVServerOptions; +function setDefaultServerOptions(options) { + var def = new WebDAVServerOptions(); + if (!options) + return def; + for (var name_1 in def) + if (options[name_1] === undefined) + options[name_1] = def[name_1]; + return options; +} +exports.setDefaultServerOptions = setDefaultServerOptions; diff --git a/lib/server/commands/Commands.js b/lib/server/commands/Commands.js index ab487d9a..a9c6ee9b 100644 --- a/lib/server/commands/Commands.js +++ b/lib/server/commands/Commands.js @@ -5,8 +5,10 @@ var Proppatch_1 = require("./Proppatch"); var Propfind_1 = require("./Propfind"); var Options_1 = require("./Options"); var Delete_1 = require("./Delete"); +var Unlock_1 = require("./Unlock"); var Mkcol_1 = require("./Mkcol"); var Copy_1 = require("./Copy"); +var Lock_1 = require("./Lock"); var Move_1 = require("./Move"); var Head_1 = require("./Head"); var Post_1 = require("./Post"); @@ -18,8 +20,10 @@ exports.default = { Propfind: Propfind_1.default, Options: Options_1.default, Delete: Delete_1.default, + Unlock: Unlock_1.default, Mkcol: Mkcol_1.default, Copy: Copy_1.default, + Lock: Lock_1.default, Move: Move_1.default, Head: Head_1.default, Post: Post_1.default, diff --git a/lib/server/commands/Copy.js b/lib/server/commands/Copy.js index 9b0cb4e0..07d8d544 100644 --- a/lib/server/commands/Copy.js +++ b/lib/server/commands/Copy.js @@ -32,62 +32,68 @@ function copyAllProperties(source, destination, callback) { } }); } -function copy(source, rDest, destination, callback) { +function copy(arg, source, rDest, destination, callback) { function _(error, cb) { if (error) callback(error); else cb(); } - source.type(function (e, type) { return _(e, function () { - var dest = rDest.fsManager.newResource(destination.toString(), destination.fileName(), type, rDest); - dest.create(function (e) { return _(e, function () { - rDest.addChild(dest, function (e) { return _(e, function () { - copyAllProperties(source, dest, function (e) { return _(e, function () { - if (!type.isFile) { - next(); - return; - } - source.read(function (e, data) { return _(e, function () { - dest.write(data, function (e) { return _(e, next); }); - }); }); - function next() { - if (!type.isDirectory) { - callback(null); - return; - } - source.getChildren(function (e, children) { return _(e, function () { - var nb = children.length; - function done(error) { - if (nb <= 0) - return; - if (error) { - nb = -1; - callback(e); + arg.requirePrivilege(['canGetType', 'canRead', 'canGetChildren', 'canGetProperties'], source, function () { + arg.requirePrivilege(['canAddChild'], rDest, function () { + source.type(function (e, type) { return _(e, function () { + var dest = rDest.fsManager.newResource(destination.toString(), destination.fileName(), type, rDest); + arg.requirePrivilege(['canCreate', 'canSetProperty', 'canWrite'], dest, function () { + dest.create(function (e) { return _(e, function () { + rDest.addChild(dest, function (e) { return _(e, function () { + copyAllProperties(source, dest, function (e) { return _(e, function () { + if (!type.isFile) { + next(); return; } - --nb; - if (nb === 0) - callback(null); - } - if (nb === 0) { - callback(null); - return; - } - children.forEach(function (child) { - child.webName(function (e, name) { - if (e) - done(e); - else - copy(child, dest, destination.getChildPath(name), done); - }); - }); + source.read(function (e, data) { return _(e, function () { + dest.write(data, function (e) { return _(e, next); }); + }); }); + function next() { + if (!type.isDirectory) { + callback(null); + return; + } + source.getChildren(function (e, children) { return _(e, function () { + var nb = children.length; + function done(error) { + if (nb <= 0) + return; + if (error) { + nb = -1; + callback(e); + return; + } + --nb; + if (nb === 0) + callback(null); + } + if (nb === 0) { + callback(null); + return; + } + children.forEach(function (child) { + child.webName(function (e, name) { + if (e) + done(e); + else + copy(arg, child, dest, destination.getChildPath(name), done); + }); + }); + }); }); + } + }); }); }); }); - } - }); }); + }); }); + }); }); }); - }); }); - }); }); + }); + }); } function default_1(arg, callback) { arg.getResource(function (e, source) { @@ -112,79 +118,83 @@ function default_1(arg, callback) { callback(); return; } - source.type(function (e, type) { - if (e) { - arg.setCode(WebDAVRequest_1.HTTPCodes.InternalServerError); - callback(); - return; - } - function done(overridded) { - copy(source, rDest, destination, function (e) { - if (e) - arg.setCode(WebDAVRequest_1.HTTPCodes.InternalServerError); - else if (overridded) - arg.setCode(WebDAVRequest_1.HTTPCodes.NoContent); - else - arg.setCode(WebDAVRequest_1.HTTPCodes.Created); - callback(); - }); - } - var nb = 0; - function go(error, destCollision) { - if (nb <= 0) - return; - if (error) { - nb = -1; - arg.setCode(WebDAVRequest_1.HTTPCodes.InternalServerError); - callback(); - return; - } - if (destCollision) { - nb = -1; - if (!overwrite) { + arg.requirePrivilege(['canGetType'], source, function () { + arg.requirePrivilege(['canGetChildren'], rDest, function () { + source.type(function (e, type) { + if (e) { arg.setCode(WebDAVRequest_1.HTTPCodes.InternalServerError); callback(); return; } - destCollision.type(function (e, destType) { - if (e) { - callback(e); + function done(overridded) { + copy(arg, source, rDest, destination, function (e) { + if (e) + arg.setCode(WebDAVRequest_1.HTTPCodes.InternalServerError); + else if (overridded) + arg.setCode(WebDAVRequest_1.HTTPCodes.NoContent); + else + arg.setCode(WebDAVRequest_1.HTTPCodes.Created); + callback(); + }); + } + var nb = 0; + function go(error, destCollision) { + if (nb <= 0) return; - } - if (destType !== type) { + if (error) { + nb = -1; arg.setCode(WebDAVRequest_1.HTTPCodes.InternalServerError); callback(); return; } - destCollision.delete(function (e) { - if (e) { - callback(e); + if (destCollision) { + nb = -1; + if (!overwrite) { + arg.setCode(WebDAVRequest_1.HTTPCodes.InternalServerError); + callback(); return; } - done(true); + destCollision.type(function (e, destType) { + if (e) { + callback(e); + return; + } + if (destType !== type) { + arg.setCode(WebDAVRequest_1.HTTPCodes.InternalServerError); + callback(); + return; + } + destCollision.delete(function (e) { + if (e) { + callback(e); + return; + } + done(true); + }); + }); + return; + } + --nb; + if (nb === 0) { + done(false); + } + } + rDest.getChildren(function (e, children) { + if (e) { + go(e, null); + return; + } + nb += children.length; + if (nb === 0) { + done(false); + return; + } + children.forEach(function (child) { + child.webName(function (e, name) { + go(e, name === destination.fileName() ? child : null); + }); }); }); - return; - } - --nb; - if (nb === 0) { - done(false); - } - } - rDest.getChildren(function (e, children) { - if (e) { - go(e, null); - return; - } - nb += children.length; - if (nb === 0) { - done(false); - return; - } - children.forEach(function (child) { - child.webName(function (e, name) { - go(e, name === destination.fileName() ? child : null); - }); }); }); }); diff --git a/lib/server/commands/Delete.js b/lib/server/commands/Delete.js index d01a9c8e..d28fd88e 100644 --- a/lib/server/commands/Delete.js +++ b/lib/server/commands/Delete.js @@ -8,12 +8,14 @@ function default_1(arg, callback) { callback(); return; } - r.delete(function (e) { - if (e) - arg.setCode(WebDAVRequest_1.HTTPCodes.InternalServerError); - else - arg.setCode(WebDAVRequest_1.HTTPCodes.OK); - callback(); + arg.requirePrivilege(['canDelete'], r, function () { + r.delete(function (e) { + if (e) + arg.setCode(WebDAVRequest_1.HTTPCodes.InternalServerError); + else + arg.setCode(WebDAVRequest_1.HTTPCodes.OK); + callback(); + }); }); }); } diff --git a/lib/server/commands/Put.js b/lib/server/commands/Put.js index f38741cc..cf0ac0c9 100644 --- a/lib/server/commands/Put.js +++ b/lib/server/commands/Put.js @@ -15,20 +15,24 @@ function createResource(arg, callback, validCallback) { callback(); return; } - var resource = r.fsManager.newResource(arg.uri, path.basename(arg.uri), Resource_1.ResourceType.File, r); - resource.create(function (e) { - if (e) { - arg.setCode(WebDAVRequest_1.HTTPCodes.InternalServerError); - callback(); - return; - } - r.addChild(resource, function (e) { - if (e) { - arg.setCode(WebDAVRequest_1.HTTPCodes.InternalServerError); - callback(); - } - else - validCallback(resource); + arg.requirePrivilege(['canAddChild'], r, function () { + var resource = r.fsManager.newResource(arg.uri, path.basename(arg.uri), Resource_1.ResourceType.File, r); + arg.requirePrivilege(['canCreate', 'canWrite'], resource, function () { + resource.create(function (e) { + if (e) { + arg.setCode(WebDAVRequest_1.HTTPCodes.InternalServerError); + callback(); + return; + } + r.addChild(resource, function (e) { + if (e) { + arg.setCode(WebDAVRequest_1.HTTPCodes.InternalServerError); + callback(); + } + else + validCallback(resource); + }); + }); }); }); }); @@ -37,12 +41,14 @@ function default_1(arg, callback) { arg.getResource(function (e, r) { if (arg.contentLength === 0) { if (r) { - r.write(new Buffer(0), function (e) { - if (e) - arg.setCode(WebDAVRequest_1.HTTPCodes.InternalServerError); - else - arg.setCode(WebDAVRequest_1.HTTPCodes.OK); - callback(); + arg.requirePrivilege(['canWrite'], r, function () { + r.write(new Buffer(0), function (e) { + if (e) + arg.setCode(WebDAVRequest_1.HTTPCodes.InternalServerError); + else + arg.setCode(WebDAVRequest_1.HTTPCodes.OK); + callback(); + }); }); return; } @@ -65,12 +71,14 @@ function default_1(arg, callback) { }); return; } - r.write(data, function (e) { - if (e) - arg.setCode(WebDAVRequest_1.HTTPCodes.InternalServerError); - else - arg.setCode(WebDAVRequest_1.HTTPCodes.OK); - callback(); + arg.requirePrivilege(['canWrite'], r, function () { + r.write(data, function (e) { + if (e) + arg.setCode(WebDAVRequest_1.HTTPCodes.InternalServerError); + else + arg.setCode(WebDAVRequest_1.HTTPCodes.OK); + callback(); + }); }); } }); diff --git a/lib/user/IUser.d.ts b/lib/user/IUser.d.ts new file mode 100644 index 00000000..e5bb9579 --- /dev/null +++ b/lib/user/IUser.d.ts @@ -0,0 +1,6 @@ +export interface IUser { + isAdministrator: boolean; + isDefaultUser: boolean; + password: string; + username: string; +} diff --git a/lib/user/User.js b/lib/user/IUser.js similarity index 100% rename from lib/user/User.js rename to lib/user/IUser.js diff --git a/lib/user/IUserManager.d.ts b/lib/user/IUserManager.d.ts new file mode 100644 index 00000000..9c56157c --- /dev/null +++ b/lib/user/IUserManager.d.ts @@ -0,0 +1,6 @@ +import { IUser } from './IUser'; +export interface IUserManager { + getUserByName(name: string, callback: (error: Error, user: IUser) => void): any; + getDefaultUser(callback: (user: IUser) => void): any; + getUsers(callback: (error: Error, users: IUser[]) => void): any; +} diff --git a/lib/user/IUserManager.js b/lib/user/IUserManager.js new file mode 100644 index 00000000..ce03781e --- /dev/null +++ b/lib/user/IUserManager.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/lib/user/User.d.ts b/lib/user/User.d.ts deleted file mode 100644 index 0d0b5837..00000000 --- a/lib/user/User.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -export interface IUser { -} diff --git a/lib/user/authentication/HTTPAuthentication.d.ts b/lib/user/authentication/HTTPAuthentication.d.ts new file mode 100644 index 00000000..13ceadda --- /dev/null +++ b/lib/user/authentication/HTTPAuthentication.d.ts @@ -0,0 +1,8 @@ +import { MethodCallArgs } from '../../server/MethodCallArgs'; +import { IUserManager } from '../IUserManager'; +import { IUser } from '../IUser'; +export interface HTTPAuthentication { + realm: string; + askForAuthentication(): any; + getUser(arg: MethodCallArgs, userManager: IUserManager, callback: (error: Error, user: IUser) => void): any; +} diff --git a/lib/user/authentication/HTTPAuthentication.js b/lib/user/authentication/HTTPAuthentication.js new file mode 100644 index 00000000..ce03781e --- /dev/null +++ b/lib/user/authentication/HTTPAuthentication.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/lib/user/authentication/HTTPBasicAuthentication.d.ts b/lib/user/authentication/HTTPBasicAuthentication.d.ts new file mode 100644 index 00000000..273e4279 --- /dev/null +++ b/lib/user/authentication/HTTPBasicAuthentication.d.ts @@ -0,0 +1,12 @@ +import { HTTPAuthentication } from './HTTPAuthentication'; +import { MethodCallArgs } from '../../server/MethodCallArgs'; +import { IUserManager } from '../IUserManager'; +import { IUser } from '../IUser'; +export declare class HTTPBasicAuthentication implements HTTPAuthentication { + realm: string; + constructor(realm?: string); + askForAuthentication(): { + 'WWW-Authenticate': string; + }; + getUser(arg: MethodCallArgs, userManager: IUserManager, callback: (error: Error, user: IUser) => void): void; +} diff --git a/lib/user/authentication/HTTPBasicAuthentication.js b/lib/user/authentication/HTTPBasicAuthentication.js new file mode 100644 index 00000000..4fd486d4 --- /dev/null +++ b/lib/user/authentication/HTTPBasicAuthentication.js @@ -0,0 +1,48 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var Errors_1 = require("../../Errors"); +var HTTPBasicAuthentication = (function () { + function HTTPBasicAuthentication(realm) { + if (realm === void 0) { realm = 'realm'; } + this.realm = realm; + } + HTTPBasicAuthentication.prototype.askForAuthentication = function () { + return { + 'WWW-Authenticate': 'Basic realm="' + this.realm + '"' + }; + }; + HTTPBasicAuthentication.prototype.getUser = function (arg, userManager, callback) { + var onError = function (error) { + userManager.getDefaultUser(function (defaultUser) { + callback(error, defaultUser); + }); + }; + var authHeader = arg.findHeader('Authorization'); + if (!authHeader) { + onError(Errors_1.Errors.MissingAuthorisationHeader); + return; + } + if (!/^Basic \s*[a-zA-Z0-9]+=*\s*$/.test(authHeader)) { + onError(Errors_1.Errors.WrongHeaderFormat); + return; + } + var value = /^Basic \s*([a-zA-Z0-9]+=*)\s*$/.exec(authHeader)[1]; + userManager.getUsers(function (e, users) { + if (e) { + onError(e); + return; + } + for (var _i = 0, users_1 = users; _i < users_1.length; _i++) { + var user = users_1[_i]; + var expected = new Buffer(user.username + ':' + user.password).toString('base64'); + if (value === expected) { + callback(Errors_1.Errors.None, user); + return; + } + } + onError(Errors_1.Errors.BadAuthentication); + }); + }; + return HTTPBasicAuthentication; +}()); +exports.HTTPBasicAuthentication = HTTPBasicAuthentication; diff --git a/lib/user/authentication/HTTPDigestAuthentication.d.ts b/lib/user/authentication/HTTPDigestAuthentication.d.ts new file mode 100644 index 00000000..ef9f769e --- /dev/null +++ b/lib/user/authentication/HTTPDigestAuthentication.d.ts @@ -0,0 +1,14 @@ +import { HTTPAuthentication } from './HTTPAuthentication'; +import { MethodCallArgs } from '../../server/MethodCallArgs'; +import { IUserManager } from '../IUserManager'; +import { IUser } from '../IUser'; +export declare class HTTPDigestAuthentication implements HTTPAuthentication { + realm: string; + nonceSize: number; + constructor(realm?: string, nonceSize?: number); + generateNonce(): string; + askForAuthentication(): { + 'WWW-Authenticate': string; + }; + getUser(arg: MethodCallArgs, userManager: IUserManager, callback: (error: Error, user: IUser) => void): void; +} diff --git a/lib/user/authentication/HTTPDigestAuthentication.js b/lib/user/authentication/HTTPDigestAuthentication.js new file mode 100644 index 00000000..26445f2c --- /dev/null +++ b/lib/user/authentication/HTTPDigestAuthentication.js @@ -0,0 +1,70 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var Errors_1 = require("../../Errors"); +var crypto = require("crypto"); +function md5(value) { + return crypto.createHash('md5').update(value).digest("hex"); +} +var HTTPDigestAuthentication = (function () { + function HTTPDigestAuthentication(realm, nonceSize) { + if (realm === void 0) { realm = 'realm'; } + if (nonceSize === void 0) { nonceSize = 50; } + this.realm = realm; + this.nonceSize = nonceSize; + } + HTTPDigestAuthentication.prototype.generateNonce = function () { + var buffer = new Buffer(this.nonceSize); + for (var i = 0; i < buffer.length; ++i) + buffer[i] = Math.ceil(Math.random() * 256); + return md5(buffer); + }; + HTTPDigestAuthentication.prototype.askForAuthentication = function () { + return { + 'WWW-Authenticate': 'Digest realm="' + this.realm + '", qop="auth,auth-int", nonce="' + this.generateNonce() + '", opaque="' + this.generateNonce() + '"' + }; + }; + HTTPDigestAuthentication.prototype.getUser = function (arg, userManager, callback) { + var _this = this; + var onError = function (error) { + userManager.getDefaultUser(function (defaultUser) { + callback(error, defaultUser); + }); + }; + var authHeader = arg.findHeader('Authorization'); + if (!authHeader) { + onError(Errors_1.Errors.MissingAuthorisationHeader); + return; + } + if (!/^Digest (\s*[a-zA-Z]+\s*=\s*"(\\"|[^"])+"\s*(,|$))+$/.test(authHeader)) { + onError(Errors_1.Errors.WrongHeaderFormat); + return; + } + authHeader = authHeader.substring(authHeader.indexOf(' ') + 1); + var authProps = {}; + var rex = /([a-zA-Z]+)\s*=\s*"((?:\\"|[^"])+)"/g; + var match = rex.exec(authHeader); + while (match) { + authProps[match[1]] = match[2]; + match = rex.exec(authHeader); + } + if (!(authProps.username && authProps.nonce && authProps.nc && authProps.cnonce && authProps.qop && authProps.response)) { + onError(Errors_1.Errors.AuenticationPropertyMissing); + return; + } + userManager.getUserByName(authProps.username, function (e, user) { + if (e) { + onError(e); + return; + } + var ha1 = md5(authProps.username + ":" + _this.realm + ":" + user.password ? user.password : ''); + var ha2 = md5(arg.request.method.toString().toUpperCase() + ":" + arg.uri); + var result = md5(ha1 + ":" + authProps.nonce + ":" + authProps.nc + ":" + authProps.cnonce + ":" + authProps.qop + ":" + ha2); + if (result.toLowerCase() === authProps.response.toLowerCase()) + callback(Errors_1.Errors.None, user); + else + onError(Errors_1.Errors.BadAuthentication); + }); + }; + return HTTPDigestAuthentication; +}()); +exports.HTTPDigestAuthentication = HTTPDigestAuthentication; diff --git a/lib/user/privilege/FakePrivilegeManager.d.ts b/lib/user/privilege/FakePrivilegeManager.d.ts new file mode 100644 index 00000000..58268d97 --- /dev/null +++ b/lib/user/privilege/FakePrivilegeManager.d.ts @@ -0,0 +1,17 @@ +import { SimplePrivilegeManager } from './SimplePrivilegeManager'; +import { hasNoWriteLock } from './IPrivilegeManager'; +export declare class FakePrivilegeManager extends SimplePrivilegeManager { + constructor(); + canCreate: (arg: any, resource: any, callback: any) => any; + canDelete: typeof hasNoWriteLock; + canWrite: typeof hasNoWriteLock; + canRead: (arg: any, resource: any, callback: any) => any; + canListLocks: (arg: any, resource: any, callback: any) => any; + canSetLock: typeof hasNoWriteLock; + canGetAvailableLocks: (arg: any, resource: any, callback: any) => any; + canAddChild: typeof hasNoWriteLock; + canRemoveChild: typeof hasNoWriteLock; + canGetChildren: (arg: any, resource: any, callback: any) => any; + canSetProperty: typeof hasNoWriteLock; + canGetProperty: (arg: any, resource: any, callback: any) => any; +} diff --git a/lib/user/privilege/FakePrivilegeManager.js b/lib/user/privilege/FakePrivilegeManager.js new file mode 100644 index 00000000..ee8bb87f --- /dev/null +++ b/lib/user/privilege/FakePrivilegeManager.js @@ -0,0 +1,35 @@ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var SimplePrivilegeManager_1 = require("./SimplePrivilegeManager"); +var IPrivilegeManager_1 = require("./IPrivilegeManager"); +var FakePrivilegeManager = (function (_super) { + __extends(FakePrivilegeManager, _super); + function FakePrivilegeManager() { + var _this = _super.call(this) || this; + _this.canCreate = function (arg, resource, callback) { return callback(null, true); }; + _this.canDelete = IPrivilegeManager_1.hasNoWriteLock; + _this.canWrite = IPrivilegeManager_1.hasNoWriteLock; + _this.canRead = function (arg, resource, callback) { return callback(null, true); }; + _this.canListLocks = function (arg, resource, callback) { return callback(null, true); }; + _this.canSetLock = IPrivilegeManager_1.hasNoWriteLock; + _this.canGetAvailableLocks = function (arg, resource, callback) { return callback(null, true); }; + _this.canAddChild = IPrivilegeManager_1.hasNoWriteLock; + _this.canRemoveChild = IPrivilegeManager_1.hasNoWriteLock; + _this.canGetChildren = function (arg, resource, callback) { return callback(null, true); }; + _this.canSetProperty = IPrivilegeManager_1.hasNoWriteLock; + _this.canGetProperty = function (arg, resource, callback) { return callback(null, true); }; + return _this; + } + return FakePrivilegeManager; +}(SimplePrivilegeManager_1.SimplePrivilegeManager)); +exports.FakePrivilegeManager = FakePrivilegeManager; diff --git a/lib/user/privilege/IPrivilegeManager.d.ts b/lib/user/privilege/IPrivilegeManager.d.ts new file mode 100644 index 00000000..b2f0c7fb --- /dev/null +++ b/lib/user/privilege/IPrivilegeManager.d.ts @@ -0,0 +1,34 @@ +import { MethodCallArgs } from '../../server/MethodCallArgs'; +import { IResource } from '../../resource/IResource'; +export declare type PrivilegeManagerCallback = (error: Error, hasAccess: boolean) => void; +export declare type PrivilegeManagerMethod = (arg: MethodCallArgs, resource: IResource, callback: PrivilegeManagerCallback) => void; +export declare type BasicPrivilege = 'canCreate' | 'canDelete' | 'canMove' | 'canRename' | 'canAppend' | 'canWrite' | 'canRead' | 'canGetMimeType' | 'canGetSize' | 'canListLocks' | 'canSetLock' | 'canRemoveLock' | 'canGetAvailableLocks' | 'canGetLock' | 'canAddChild' | 'canRemoveChild' | 'canGetChildren' | 'canSetProperty' | 'canGetProperty' | 'canGetProperties' | 'canRemoveProperty' | 'canGetCreationDate' | 'canGetLastModifiedDate' | 'canGetWebName' | 'canGetType'; +export declare function requirePrivilege(privilege: string | BasicPrivilege | string[] | BasicPrivilege[], arg: MethodCallArgs, resource: IResource, callback: PrivilegeManagerCallback): void; +export interface IPrivilegeManager { + canCreate: PrivilegeManagerMethod; + canDelete: PrivilegeManagerMethod; + canMove: PrivilegeManagerMethod; + canRename: PrivilegeManagerMethod; + canAppend: PrivilegeManagerMethod; + canWrite: PrivilegeManagerMethod; + canRead: PrivilegeManagerMethod; + canGetMimeType: PrivilegeManagerMethod; + canGetSize: PrivilegeManagerMethod; + canListLocks: PrivilegeManagerMethod; + canSetLock: PrivilegeManagerMethod; + canRemoveLock: PrivilegeManagerMethod; + canGetAvailableLocks: PrivilegeManagerMethod; + canGetLock: PrivilegeManagerMethod; + canAddChild: PrivilegeManagerMethod; + canRemoveChild: PrivilegeManagerMethod; + canGetChildren: PrivilegeManagerMethod; + canSetProperty: PrivilegeManagerMethod; + canGetProperty: PrivilegeManagerMethod; + canGetProperties: PrivilegeManagerMethod; + canRemoveProperty: PrivilegeManagerMethod; + canGetCreationDate: PrivilegeManagerMethod; + canGetLastModifiedDate: PrivilegeManagerMethod; + canGetWebName: PrivilegeManagerMethod; + canGetType: PrivilegeManagerMethod; +} +export declare function hasNoWriteLock(arg: MethodCallArgs, resource: IResource, callback: PrivilegeManagerCallback): void; diff --git a/lib/user/privilege/IPrivilegeManager.js b/lib/user/privilege/IPrivilegeManager.js new file mode 100644 index 00000000..a1cb8713 --- /dev/null +++ b/lib/user/privilege/IPrivilegeManager.js @@ -0,0 +1,22 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var LockType_1 = require("../../resource/lock/LockType"); +function requirePrivilege(privilege, arg, resource, callback) { + var privileges = privilege.constructor !== Array ? [privilege] : privilege; + var pm = arg.server.privilegeManager; + go(); + function go(error, hasAccess) { + if (error === void 0) { error = null; } + if (hasAccess === void 0) { hasAccess = true; } + if (privileges.length === 0 || error || !hasAccess) { + callback(error, hasAccess); + return; + } + pm[privileges.shift()](arg, resource, go); + } +} +exports.requirePrivilege = requirePrivilege; +function hasNoWriteLock(arg, resource, callback) { + resource.getLocks(function (e, locks) { return callback(e, locks ? locks.filter(function (l) { return l.user !== arg.user && l.lockKind.type.isSame(LockType_1.LockType.Write); }).length === 0 : false); }); +} +exports.hasNoWriteLock = hasNoWriteLock; diff --git a/lib/user/privilege/SimplePrivilegeManager.d.ts b/lib/user/privilege/SimplePrivilegeManager.d.ts new file mode 100644 index 00000000..1813aa98 --- /dev/null +++ b/lib/user/privilege/SimplePrivilegeManager.d.ts @@ -0,0 +1,29 @@ +import { PrivilegeManagerMethod } from './IPrivilegeManager'; +import { IPrivilegeManager } from './IPrivilegeManager'; +export declare abstract class SimplePrivilegeManager implements IPrivilegeManager { + abstract canCreate: PrivilegeManagerMethod; + abstract canDelete: PrivilegeManagerMethod; + canMove: (arg: any, resource: any, callback: any) => void; + canRename: (arg: any, resource: any, callback: any) => void; + canAppend: (arg: any, resource: any, callback: any) => void; + abstract canWrite: PrivilegeManagerMethod; + abstract canRead: PrivilegeManagerMethod; + canGetMimeType: (arg: any, resource: any, callback: any) => void; + canGetSize: (arg: any, resource: any, callback: any) => void; + abstract canListLocks: PrivilegeManagerMethod; + abstract canSetLock: PrivilegeManagerMethod; + canRemoveLock: (arg: any, resource: any, callback: any) => void; + abstract canGetAvailableLocks: PrivilegeManagerMethod; + canGetLock: (arg: any, resource: any, callback: any) => void; + abstract canAddChild: PrivilegeManagerMethod; + abstract canRemoveChild: PrivilegeManagerMethod; + abstract canGetChildren: PrivilegeManagerMethod; + abstract canSetProperty: PrivilegeManagerMethod; + abstract canGetProperty: PrivilegeManagerMethod; + canGetProperties: (arg: any, resource: any, callback: any) => void; + canRemoveProperty: (arg: any, resource: any, callback: any) => void; + canGetCreationDate: (arg: any, resource: any, callback: any) => void; + canGetLastModifiedDate: (arg: any, resource: any, callback: any) => void; + canGetWebName: (arg: any, resource: any, callback: any) => void; + canGetType: (arg: any, resource: any, callback: any) => void; +} diff --git a/lib/user/privilege/SimplePrivilegeManager.js b/lib/user/privilege/SimplePrivilegeManager.js new file mode 100644 index 00000000..bde570c3 --- /dev/null +++ b/lib/user/privilege/SimplePrivilegeManager.js @@ -0,0 +1,29 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var SimplePrivilegeManager = (function () { + function SimplePrivilegeManager() { + var _this = this; + this.canMove = function (arg, resource, callback) { + _this.canDelete(arg, resource, function (e, v) { + if (e || !v) + callback(e, v); + else + _this.canRead(arg, resource, callback); + }); + }; + this.canRename = function (arg, resource, callback) { return _this.canWrite(arg, resource, callback); }; + this.canAppend = function (arg, resource, callback) { return _this.canWrite(arg, resource, callback); }; + this.canGetMimeType = function (arg, resource, callback) { return _this.canRead(arg, resource, callback); }; + this.canGetSize = function (arg, resource, callback) { return _this.canRead(arg, resource, callback); }; + this.canRemoveLock = function (arg, resource, callback) { return _this.canSetLock(arg, resource, callback); }; + this.canGetLock = function (arg, resource, callback) { return _this.canListLocks(arg, resource, callback); }; + this.canGetProperties = function (arg, resource, callback) { return _this.canGetProperty(arg, resource, callback); }; + this.canRemoveProperty = function (arg, resource, callback) { return _this.canSetProperty(arg, resource, callback); }; + this.canGetCreationDate = function (arg, resource, callback) { return _this.canRead(arg, resource, callback); }; + this.canGetLastModifiedDate = function (arg, resource, callback) { return _this.canRead(arg, resource, callback); }; + this.canGetWebName = function (arg, resource, callback) { return _this.canRead(arg, resource, callback); }; + this.canGetType = function (arg, resource, callback) { return _this.canRead(arg, resource, callback); }; + } + return SimplePrivilegeManager; +}()); +exports.SimplePrivilegeManager = SimplePrivilegeManager; diff --git a/lib/user/simple/SimpleUser.d.ts b/lib/user/simple/SimpleUser.d.ts new file mode 100644 index 00000000..b2566682 --- /dev/null +++ b/lib/user/simple/SimpleUser.d.ts @@ -0,0 +1,8 @@ +import { IUser } from '../IUser'; +export declare class SimpleUser implements IUser { + username: string; + password: string; + isAdministrator: boolean; + isDefaultUser: boolean; + constructor(username: string, password: string, isAdministrator: boolean, isDefaultUser: boolean); +} diff --git a/lib/user/simple/SimpleUser.js b/lib/user/simple/SimpleUser.js new file mode 100644 index 00000000..aa9858a3 --- /dev/null +++ b/lib/user/simple/SimpleUser.js @@ -0,0 +1,12 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var SimpleUser = (function () { + function SimpleUser(username, password, isAdministrator, isDefaultUser) { + this.username = username; + this.password = password; + this.isAdministrator = isAdministrator; + this.isDefaultUser = isDefaultUser; + } + return SimpleUser; +}()); +exports.SimpleUser = SimpleUser; diff --git a/lib/user/simple/SimpleUserManager.d.ts b/lib/user/simple/SimpleUserManager.d.ts new file mode 100644 index 00000000..3c148197 --- /dev/null +++ b/lib/user/simple/SimpleUserManager.d.ts @@ -0,0 +1,10 @@ +import { IUserManager } from '../IUserManager'; +import { IUser } from '../IUser'; +export declare class SimpleUserManager implements IUserManager { + protected users: any; + constructor(); + getUserByName(name: string, callback: (error: Error, user: IUser) => void): void; + getDefaultUser(callback: (user: IUser) => void): void; + addUser(name: string, password: string, isAdmin?: boolean): void; + getUsers(callback: (error: Error, users: IUser[]) => void): void; +} diff --git a/lib/user/simple/SimpleUserManager.js b/lib/user/simple/SimpleUserManager.js new file mode 100644 index 00000000..711a96bb --- /dev/null +++ b/lib/user/simple/SimpleUserManager.js @@ -0,0 +1,31 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var SimpleUser_1 = require("./SimpleUser"); +var SimpleUserManager = (function () { + function SimpleUserManager() { + this.users = { + __default: new SimpleUser_1.SimpleUser('DefaultUser', null, false, true) + }; + } + SimpleUserManager.prototype.getUserByName = function (name, callback) { + if (!this.users[name]) + callback(new Error('User not found'), null); + else + callback(null, this.users[name]); + }; + SimpleUserManager.prototype.getDefaultUser = function (callback) { + callback(this.users.__default); + }; + SimpleUserManager.prototype.addUser = function (name, password, isAdmin) { + if (isAdmin === void 0) { isAdmin = false; } + this.users[name] = new SimpleUser_1.SimpleUser(name, password, isAdmin, false); + }; + SimpleUserManager.prototype.getUsers = function (callback) { + var users = []; + for (var name_1 in this.users) + users.push(this.users[name_1]); + callback(null, users); + }; + return SimpleUserManager; +}()); +exports.SimpleUserManager = SimpleUserManager; diff --git a/src/resource/IResource.ts b/src/resource/IResource.ts index 263404f6..83b6f9f3 100644 --- a/src/resource/IResource.ts +++ b/src/resource/IResource.ts @@ -1,6 +1,7 @@ import { FSManager, FSPath } from '../manager/FSManager' import { XMLElement } from '../helper/XML' import { LockKind } from './lock/LockKind' +import { LockType } from './lock/LockType' import { Lock } from './lock/Lock' import * as crypto from 'crypto' @@ -53,12 +54,13 @@ export interface IResource size(callback : ReturnCallback) // ****************************** Locks ****************************** // - getLocks(lockKind : LockKind, callback : ReturnCallback) + getLocks(callback : ReturnCallback) setLock(lock : Lock, callback : SimpleCallback) - removeLock(uuid : string, owner : string, callback : ReturnCallback) + removeLock(uuid : string, callback : ReturnCallback) canLock(lockKind : LockKind, callback : ReturnCallback) getAvailableLocks(callback : ReturnCallback) - canRemoveLock(uuid : string, owner : string, callback : ReturnCallback) + canRemoveLock(uuid : string, callback : ReturnCallback) + getLock(uuid : string, callback : ReturnCallback) // ****************************** Children ****************************** // addChild(resource : IResource, callback : SimpleCallback) diff --git a/src/resource/std/StandardResource.ts b/src/resource/std/StandardResource.ts index d4cd2135..ee215d72 100644 --- a/src/resource/std/StandardResource.ts +++ b/src/resource/std/StandardResource.ts @@ -65,17 +65,22 @@ export abstract class StandardResource implements IResource new LockKind(LockScope.Shared, LockType.Write) ]) } - getLocks(lockKind : LockKind, callback : ReturnCallback) + getLocks(callback : ReturnCallback) { - callback(null, this.lockBag.getLocks(lockKind)); + callback(null, this.lockBag.getLocks()); } setLock(lock : Lock, callback : SimpleCallback) { const locked = this.lockBag.setLock(lock); + this.updateLastModified(); callback(locked ? null : new Error('Can\'t lock the resource.')); } - removeLock(uuid : string, owner : string, callback : ReturnCallback) + removeLock(uuid : string, callback : ReturnCallback) { + this.lockBag.removeLock(uuid); + this.updateLastModified(); + callback(null, true); + /* this.getChildren((e, children) => { if(e) { @@ -85,7 +90,7 @@ export abstract class StandardResource implements IResource let nb = children.length + 1; children.forEach((child) => { - child.canRemoveLock(uuid, owner, go); + child.canRemoveLock(uuid, go); }); go(null, true); @@ -106,21 +111,25 @@ export abstract class StandardResource implements IResource --nb; if(nb === 0) { - this.lockBag.removeLock(uuid, owner); + this.lockBag.removeLock(uuid); this.updateLastModified(); callback(null, true); } } - }) + })*/ } - canRemoveLock(uuid : string, owner : string, callback : ReturnCallback) + canRemoveLock(uuid : string, callback : ReturnCallback) { - callback(null, this.lockBag.canRemoveLock(uuid, owner)); + callback(null, this.lockBag.canRemoveLock(uuid)); } canLock(lockKind : LockKind, callback : ReturnCallback) { callback(null, this.lockBag.canLock(lockKind)); } + getLock(uuid : string, callback : ReturnCallback) + { + callback(null, this.lockBag.getLock(uuid)); + } // ****************************** Properties ****************************** // setProperty(name : string, value : ResourcePropertyValue, callback : SimpleCallback) diff --git a/src/server/MethodCallArgs.ts b/src/server/MethodCallArgs.ts index da92bc8b..3e28d221 100644 --- a/src/server/MethodCallArgs.ts +++ b/src/server/MethodCallArgs.ts @@ -1,7 +1,11 @@ +import { requirePrivilege, BasicPrivilege } from '../user/privilege/IPrivilegeManager' import { IResource, ReturnCallback } from '../resource/Resource' import { XML, XMLElement } from '../helper/XML' import { WebDAVServer } from '../server/WebDAVServer' +import { HTTPCodes } from './HTTPCodes' import { FSPath } from '../manager/FSManager' +import { Errors } from '../Errors' +import { IUser } from '../user/IUser' import * as http from 'http' import * as url from 'url' @@ -14,11 +18,13 @@ export class MethodCallArgs uri : string data : string + user : IUser - constructor( + protected constructor( public server : WebDAVServer, public request : http.IncomingMessage, public response : http.ServerResponse, + public exit : () => void, public callback : () => void ) { this.contentLength = parseInt(this.findHeader('Content-length', '0'), 10); @@ -29,6 +35,73 @@ export class MethodCallArgs this.path = new FSPath(this.uri); } + static create( + server : WebDAVServer, + request : http.IncomingMessage, + response : http.ServerResponse, + callback : (error : Error, mca : MethodCallArgs) => void) + { + const mca = new MethodCallArgs(server, request, response, null, null); + + mca.askForAuthentication(false, (e) => { + if(e) + { + callback(e, mca); + return; + } + + server.httpAuthentication.getUser(mca, server.userManager, (e, user) => { + mca.user = user; + if(e) + { + if(e === Errors.MissingAuthorisationHeader) + e = null; + } + + callback(e, mca); + }) + }) + } + + requireCustomPrivilege(privileges : string | string[], resource : IResource, callback : () => void) + { + requirePrivilege(privileges, this, resource, (e, can) => { + if(e) + { + this.setCode(HTTPCodes.InternalServerError); + this.exit(); + return; + } + + if(!can) + { + this.setCode(HTTPCodes.Unauthorized); + this.exit(); + return; + } + + callback(); + }); + } + requirePrivilege(privileges : BasicPrivilege | BasicPrivilege[], resource : IResource, callback : () => void) + { + this.requireCustomPrivilege(privileges, resource, callback); + } + + askForAuthentication(checkForUser : boolean, callback : (error : Error) => void) + { + if(checkForUser && this.user !== null && !this.user.isDefaultUser) + { + callback(new Error('Already authenticated')) + return; + } + + const auth = this.server.httpAuthentication.askForAuthentication(); + for(const name in auth) + this.response.setHeader(name, auth[name]); + callback(null); + } + accept(regex : RegExp[]) : number { const accepts = this.findHeader('Accept', 'text/xml').split(','); diff --git a/src/server/WebDAVServer.ts b/src/server/WebDAVServer.ts index 80e49c0f..d019d6ae 100644 --- a/src/server/WebDAVServer.ts +++ b/src/server/WebDAVServer.ts @@ -1,7 +1,13 @@ +import { WebDAVServerOptions, setDefaultServerOptions } from './WebDAVServerOptions' import { HTTPCodes, MethodCallArgs, WebDAVRequest } from './WebDAVRequest' import { RootResource, IResource, ReturnCallback } from '../resource/Resource' -import { WebDAVServerOptions } from './WebDAVServerOptions' +import { FakePrivilegeManager } from '../user/privilege/FakePrivilegeManager' +import { HTTPAuthentication } from '../user/authentication/HTTPAuthentication' +import { IPrivilegeManager } from '../user/privilege/IPrivilegeManager' +import { SimpleUserManager } from '../user/simple/SimpleUserManager' import { FSManager, FSPath } from '../manager/FSManager' +import { IUserManager } from '../user/IUserManager' +import { Errors } from '../Errors' import Commands from './commands/Commands' import * as http from 'http' @@ -9,22 +15,29 @@ export { WebDAVServerOptions } from './WebDAVServerOptions' export class WebDAVServer { + public httpAuthentication : HTTPAuthentication + public privilegeManager : IPrivilegeManager public rootResource : IResource + public userManager : IUserManager + public options : WebDAVServerOptions public methods : object protected beforeManagers : WebDAVRequest[] protected afterManagers : WebDAVRequest[] protected unknownMethod : WebDAVRequest - protected options : WebDAVServerOptions protected server : http.Server constructor(options ?: WebDAVServerOptions) { this.beforeManagers = []; - this.rootResource = new RootResource(); this.afterManagers = []; this.methods = {}; - this.options = options; + this.options = setDefaultServerOptions(options); + + this.httpAuthentication = this.options.httpAuthentication; + this.privilegeManager = this.options.privilegeManager; + this.rootResource = this.options.rootResource; + this.userManager = this.options.userManager; // Implement all methods in commands/Commands.ts for(const k in Commands) @@ -112,40 +125,51 @@ export class WebDAVServer if(!method) method = this.unknownMethod; - const base : MethodCallArgs = this.createMethodCallArgs(req, res) - - if(!method.chunked) - { - let data = ''; - const go = () => + MethodCallArgs.create(this, req, res, (e, base) => { + if(e) { - base.data = data; - this.invokeBeforeRequest(base, () => { - method(base, () => - { - res.end(); - this.invokeAfterRequest(base, null); - }); - }) + if(e === Errors.AuenticationPropertyMissing) + base.setCode(HTTPCodes.Unauthorized); + else + base.setCode(HTTPCodes.InternalServerError); + res.end(); + return; } - - if(base.contentLength === 0) - { - go(); - } - else + + if(!method.chunked) { - req.on('data', (chunk) => { - data += chunk.toString(); - if(data.length >= base.contentLength) - { - if(data.length > base.contentLength) - data = data.substring(0, base.contentLength); - go(); - } - }); + let data = ''; + const go = () => + { + base.data = data; + this.invokeBeforeRequest(base, () => { + base.exit = () => + { + res.end(); + this.invokeAfterRequest(base, null); + }; + method(base, base.exit); + }) + } + + if(base.contentLength === 0) + { + go(); + } + else + { + req.on('data', (chunk) => { + data += chunk.toString(); + if(data.length >= base.contentLength) + { + if(data.length > base.contentLength) + data = data.substring(0, base.contentLength); + go(); + } + }); + } } - } + }) }) this.server.listen(port); } @@ -175,16 +199,6 @@ export class WebDAVServer this.afterManagers.push(manager); } - protected createMethodCallArgs(req : http.IncomingMessage, res : http.ServerResponse) : MethodCallArgs - { - return new MethodCallArgs( - this, - req, - res, - null - ) - } - protected normalizeMethodName(method : string) : string { return method.toLowerCase(); diff --git a/src/server/WebDAVServerOptions.ts b/src/server/WebDAVServerOptions.ts index ed68ad84..bc809d90 100644 --- a/src/server/WebDAVServerOptions.ts +++ b/src/server/WebDAVServerOptions.ts @@ -1,6 +1,34 @@ +import { HTTPBasicAuthentication } from '../user/authentication/HTTPBasicAuthentication' +import { FakePrivilegeManager } from '../user/privilege/FakePrivilegeManager' +import { HTTPAuthentication } from '../user/authentication/HTTPAuthentication' +import { IPrivilegeManager } from '../user/privilege/IPrivilegeManager' +import { SimpleUserManager } from '../user/simple/SimpleUserManager' +import { RootResource } from '../resource/std/RootResource' +import { IUserManager } from '../user/IUserManager' +import { IResource } from '../resource/IResource' export class WebDAVServerOptions { + requireAuthentification ?: boolean = false + httpAuthentication ?: HTTPAuthentication = new HTTPBasicAuthentication('default realm') + privilegeManager ?: IPrivilegeManager = new FakePrivilegeManager() + rootResource ?: IResource = new RootResource() + userManager ?: IUserManager = new SimpleUserManager() + lockTimeout ?: number = 3600 port ?: number = 1900 } export default WebDAVServerOptions; + +export function setDefaultServerOptions(options : WebDAVServerOptions) : WebDAVServerOptions +{ + const def = new WebDAVServerOptions(); + + if(!options) + return def; + + for(const name in def) + if(options[name] === undefined) + options[name] = def[name]; + + return options; +} diff --git a/src/server/commands/Commands.ts b/src/server/commands/Commands.ts index fb2866b5..813097f4 100644 --- a/src/server/commands/Commands.ts +++ b/src/server/commands/Commands.ts @@ -3,8 +3,10 @@ import Proppatch from './Proppatch' import Propfind from './Propfind' import Options from './Options' import Delete from './Delete' +import Unlock from './Unlock' import Mkcol from './Mkcol' import Copy from './Copy' +import Lock from './Lock' import Move from './Move' import Head from './Head' import Post from './Post' @@ -17,8 +19,10 @@ export default { Propfind, Options, Delete, + Unlock, Mkcol, Copy, + Lock, Move, Head, Post, diff --git a/src/server/commands/Copy.ts b/src/server/commands/Copy.ts index 108f5b49..7b69cf16 100644 --- a/src/server/commands/Copy.ts +++ b/src/server/commands/Copy.ts @@ -1,8 +1,8 @@ import { HTTPCodes, MethodCallArgs, WebDAVRequest } from '../WebDAVRequest' -import { IResource, ResourceType, Simpl_ } from '../../resource/Resource' +import { IResource, ResourceType, SimpleCallback } from '../../resource/Resource' import { FSPath } from '../../manager/FSManager' -function copyAllProperties(source : IResource, destination : IResource, callback : Simpl_) +function copyAllProperties(source : IResource, destination : IResource, callback : SimpleCallback) { source.getProperties((e, props) => { if(e) @@ -44,7 +44,7 @@ function copyAllProperties(source : IResource, destination : IResource, callback }) } -function copy(source : IResource, rDest : IResource, destination : FSPath, callback : Simpl_) +function copy(arg : MethodCallArgs, source : IResource, rDest : IResource, destination : FSPath, callback : SimpleCallback) { // Error wrapper function _(error : Error, cb) @@ -55,68 +55,74 @@ function copy(source : IResource, rDest : IResource, destination : FSPath, callb cb(); } - source.type((e, type) => _(e, () => { - const dest = rDest.fsManager.newResource(destination.toString(), destination.fileName(), type, rDest); + arg.requirePrivilege([ 'canGetType', 'canRead', 'canGetChildren', 'canGetProperties' ], source, () => { + arg.requirePrivilege([ 'canAddChild' ], rDest, () => { + source.type((e, type) => _(e, () => { + const dest = rDest.fsManager.newResource(destination.toString(), destination.fileName(), type, rDest); - dest.create((e) => _(e, () => { - rDest.addChild(dest, (e) => _(e, () => { - copyAllProperties(source, dest, (e) => _(e, () => { - if(!type.isFile) - { - next(); - return; - } - - source.read((e, data) => _(e, () => { - dest.write(data, (e) => _(e, next)) - })) - - function next() - { - if(!type.isDirectory) - { - callback(null); - return; - } - - source.getChildren((e, children) => _(e, () => { - let nb = children.length; - function done(error) - { - if(nb <= 0) - return; - if(error) + arg.requirePrivilege([ 'canCreate', 'canSetProperty', 'canWrite' ], dest, () => { + dest.create((e) => _(e, () => { + rDest.addChild(dest, (e) => _(e, () => { + copyAllProperties(source, dest, (e) => _(e, () => { + if(!type.isFile) { - nb = -1; - callback(e); + next(); return; } - --nb; - if(nb === 0) - callback(null); - } + source.read((e, data) => _(e, () => { + dest.write(data, (e) => _(e, next)) + })) - if(nb === 0) - { - callback(null); - return; - } + function next() + { + if(!type.isDirectory) + { + callback(null); + return; + } - children.forEach((child) => { - child.webName((e, name) => { - if(e) - done(e); - else - copy(child, dest, destination.getChildPath(name), done); - }) - }) + source.getChildren((e, children) => _(e, () => { + let nb = children.length; + function done(error) + { + if(nb <= 0) + return; + if(error) + { + nb = -1; + callback(e); + return; + } + + --nb; + if(nb === 0) + callback(null); + } + + if(nb === 0) + { + callback(null); + return; + } + + children.forEach((child) => { + child.webName((e, name) => { + if(e) + done(e); + else + copy(arg, child, dest, destination.getChildPath(name), done); + }) + }) + })) + } + })) })) - } - })) + })) + }) })) - })) - })) + }) + }) } export default function(arg : MethodCallArgs, callback) @@ -151,102 +157,106 @@ export default function(arg : MethodCallArgs, callback) return; } - source.type((e, type) => { - if(e) - { - arg.setCode(HTTPCodes.InternalServerError); - callback(); - return; - } - - function done(overridded : boolean) - { - copy(source, rDest, destination, (e) => { + arg.requirePrivilege([ 'canGetType' ], source, () => { + arg.requirePrivilege([ 'canGetChildren' ], rDest, () => { + source.type((e, type) => { if(e) - arg.setCode(HTTPCodes.InternalServerError); - else if(overridded) - arg.setCode(HTTPCodes.NoContent); - else - arg.setCode(HTTPCodes.Created); - callback(); - }) - } - - let nb = 0; - function go(error, destCollision : IResource) - { - if(nb <= 0) - return; - if(error) - { - nb = -1; - arg.setCode(HTTPCodes.InternalServerError); - callback(); - return; - } - if(destCollision) - { - nb = -1; - - if(!overwrite) - { // No overwrite allowed + { arg.setCode(HTTPCodes.InternalServerError); callback(); return; } + + function done(overridded : boolean) + { + copy(arg, source, rDest, destination, (e) => { + if(e) + arg.setCode(HTTPCodes.InternalServerError); + else if(overridded) + arg.setCode(HTTPCodes.NoContent); + else + arg.setCode(HTTPCodes.Created); + callback(); + }) + } - destCollision.type((e, destType) => { - if(e) - { - callback(e); + let nb = 0; + function go(error, destCollision : IResource) + { + if(nb <= 0) return; - } - - if(destType !== type) - { // Type collision + if(error) + { + nb = -1; arg.setCode(HTTPCodes.InternalServerError); callback(); return; } - - destCollision.delete((e) => { - if(e) - { - callback(e); + if(destCollision) + { + nb = -1; + + if(!overwrite) + { // No overwrite allowed + arg.setCode(HTTPCodes.InternalServerError); + callback(); return; } - done(true); + destCollision.type((e, destType) => { + if(e) + { + callback(e); + return; + } + + if(destType !== type) + { // Type collision + arg.setCode(HTTPCodes.InternalServerError); + callback(); + return; + } + + destCollision.delete((e) => { + if(e) + { + callback(e); + return; + } + + done(true); + }) + }) + return; + } + + --nb; + if(nb === 0) + { + done(false); + } + } + + // Find child name collision + rDest.getChildren((e, children) => { + if(e) + { + go(e, null); + return; + } + + nb += children.length; + if(nb === 0) + { + done(false); + return; + } + children.forEach((child) => { + child.webName((e, name) => { + go(e, name === destination.fileName() ? child : null); + }) }) }) - return; - } - - --nb; - if(nb === 0) - { - done(false); - } - } - - // Find child name collision - rDest.getChildren((e, children) => { - if(e) - { - go(e, null); - return; - } - - nb += children.length; - if(nb === 0) - { - done(false); - return; - } - children.forEach((child) => { - child.webName((e, name) => { - go(e, name === destination.fileName() ? child : null); - }) }) }) }) diff --git a/src/server/commands/Delete.ts b/src/server/commands/Delete.ts index 37c2c219..6f3136e8 100644 --- a/src/server/commands/Delete.ts +++ b/src/server/commands/Delete.ts @@ -11,12 +11,14 @@ export default function(arg : MethodCallArgs, callback) return; } - r.delete((e) => { - if(e) - arg.setCode(HTTPCodes.InternalServerError); - else - arg.setCode(HTTPCodes.OK); - callback(); + arg.requirePrivilege([ 'canDelete' ], r, () => { + r.delete((e) => { + if(e) + arg.setCode(HTTPCodes.InternalServerError); + else + arg.setCode(HTTPCodes.OK); + callback(); + }) }) }) } diff --git a/src/server/commands/Put.ts b/src/server/commands/Put.ts index 53ba3d12..3b808576 100644 --- a/src/server/commands/Put.ts +++ b/src/server/commands/Put.ts @@ -18,24 +18,28 @@ function createResource(arg : MethodCallArgs, callback, validCallback : (resourc callback(); return; } - - const resource = r.fsManager.newResource(arg.uri, path.basename(arg.uri), ResourceType.File, r); - resource.create((e) => { - if(e) - { - arg.setCode(HTTPCodes.InternalServerError) - callback(); - return; - } - - r.addChild(resource, (e) => { - if(e) - { - arg.setCode(HTTPCodes.InternalServerError) - callback(); - } - else - validCallback(resource); + + arg.requirePrivilege([ 'canAddChild' ], r, () => { + const resource = r.fsManager.newResource(arg.uri, path.basename(arg.uri), ResourceType.File, r); + arg.requirePrivilege([ 'canCreate', 'canWrite' ], resource, () => { + resource.create((e) => { + if(e) + { + arg.setCode(HTTPCodes.InternalServerError) + callback(); + return; + } + + r.addChild(resource, (e) => { + if(e) + { + arg.setCode(HTTPCodes.InternalServerError) + callback(); + } + else + validCallback(resource); + }) + }) }) }) }) @@ -43,18 +47,19 @@ function createResource(arg : MethodCallArgs, callback, validCallback : (resourc export default function(arg : MethodCallArgs, callback) { - arg.getResource((e, r) => { if(arg.contentLength === 0) { // Create file if(r) { // Resource exists => empty it - r.write(new Buffer(0), (e) => { - if(e) - arg.setCode(HTTPCodes.InternalServerError) - else - arg.setCode(HTTPCodes.OK) - callback() + arg.requirePrivilege([ 'canWrite' ], r, () => { + r.write(new Buffer(0), (e) => { + if(e) + arg.setCode(HTTPCodes.InternalServerError) + else + arg.setCode(HTTPCodes.OK) + callback() + }) }) return; } @@ -82,12 +87,14 @@ export default function(arg : MethodCallArgs, callback) return; } - r.write(data, (e) => { - if(e) - arg.setCode(HTTPCodes.InternalServerError) - else - arg.setCode(HTTPCodes.OK) - callback(); + arg.requirePrivilege([ 'canWrite' ], r, () => { + r.write(data, (e) => { + if(e) + arg.setCode(HTTPCodes.InternalServerError) + else + arg.setCode(HTTPCodes.OK) + callback(); + }) }) } }) diff --git a/src/user/IUser.ts b/src/user/IUser.ts new file mode 100644 index 00000000..283a1fa2 --- /dev/null +++ b/src/user/IUser.ts @@ -0,0 +1,8 @@ + +export interface IUser +{ + isAdministrator : boolean + isDefaultUser : boolean + password : string + username : string +} diff --git a/src/user/IUserManager.ts b/src/user/IUserManager.ts new file mode 100644 index 00000000..b193b720 --- /dev/null +++ b/src/user/IUserManager.ts @@ -0,0 +1,8 @@ +import { IUser } from './IUser' + +export interface IUserManager +{ + getUserByName(name : string, callback : (error : Error, user : IUser) => void) + getDefaultUser(callback : (user : IUser) => void) + getUsers(callback : (error : Error, users : IUser[]) => void) +} diff --git a/src/user/User.ts b/src/user/User.ts deleted file mode 100644 index a026f2fb..00000000 --- a/src/user/User.ts +++ /dev/null @@ -1,3 +0,0 @@ - -export interface IUser -{ } diff --git a/src/user/authentication/HTTPAuthentication.ts b/src/user/authentication/HTTPAuthentication.ts new file mode 100644 index 00000000..ec2702e6 --- /dev/null +++ b/src/user/authentication/HTTPAuthentication.ts @@ -0,0 +1,11 @@ +import { MethodCallArgs } from '../../server/MethodCallArgs' +import { IUserManager } from '../IUserManager' +import { IUser } from '../IUser' + +export interface HTTPAuthentication +{ + realm : string + + askForAuthentication() : any + getUser(arg : MethodCallArgs, userManager : IUserManager, callback : (error : Error, user : IUser) => void) +} diff --git a/src/user/authentication/HTTPBasicAuthentication.ts b/src/user/authentication/HTTPBasicAuthentication.ts new file mode 100644 index 00000000..e6fc65d9 --- /dev/null +++ b/src/user/authentication/HTTPBasicAuthentication.ts @@ -0,0 +1,63 @@ +import { HTTPAuthentication } from './HTTPAuthentication' +import { MethodCallArgs } from '../../server/MethodCallArgs' +import { IUserManager } from '../IUserManager' +import { Errors } from '../../Errors' +import { IUser } from '../IUser' + +export class HTTPBasicAuthentication implements HTTPAuthentication +{ + constructor(public realm : string = 'realm') + { } + + askForAuthentication() + { + return { + 'WWW-Authenticate': 'Basic realm="' + this.realm + '"' + } + } + + getUser(arg : MethodCallArgs, userManager : IUserManager, callback : (error : Error, user : IUser) => void) + { + const onError = (error : Error) => + { + userManager.getDefaultUser((defaultUser) => { + callback(error, defaultUser) + }) + } + + let authHeader = arg.findHeader('Authorization') + if(!authHeader) + { + onError(Errors.MissingAuthorisationHeader) + return; + } + if(!/^Basic \s*[a-zA-Z0-9]+=*\s*$/.test(authHeader)) + { + onError(Errors.WrongHeaderFormat); + return; + } + + const value = /^Basic \s*([a-zA-Z0-9]+=*)\s*$/.exec(authHeader)[1]; + + userManager.getUsers((e, users) => { + if(e) + { + onError(e); + return; + } + + for(const user of users) + { + const expected = new Buffer(user.username + ':' + user.password).toString('base64') + + if(value === expected) + { + callback(Errors.None, user); + return; + } + } + + onError(Errors.BadAuthentication); + }); + } +} diff --git a/src/user/authentication/HTTPDigestAuthentication.ts b/src/user/authentication/HTTPDigestAuthentication.ts new file mode 100644 index 00000000..438b7af4 --- /dev/null +++ b/src/user/authentication/HTTPDigestAuthentication.ts @@ -0,0 +1,90 @@ +import { HTTPAuthentication } from './HTTPAuthentication' +import { MethodCallArgs } from '../../server/MethodCallArgs' +import { IUserManager } from '../IUserManager' +import { Errors } from '../../Errors' +import { IUser } from '../IUser' +import * as crypto from 'crypto' + +function md5(value : string | Buffer) : string +{ + return crypto.createHash('md5').update(value).digest("hex"); +} + +export class HTTPDigestAuthentication implements HTTPAuthentication +{ + constructor(public realm : string = 'realm', public nonceSize : number = 50) + { } + + generateNonce() : string + { + const buffer = new Buffer(this.nonceSize); + for(let i = 0; i < buffer.length; ++i) + buffer[i] = Math.ceil(Math.random() * 256); + + return md5(buffer); + } + + askForAuthentication() + { + return { + 'WWW-Authenticate': 'Digest realm="' + this.realm + '", qop="auth,auth-int", nonce="' + this.generateNonce() + '", opaque="' + this.generateNonce() + '"' + } + } + + getUser(arg : MethodCallArgs, userManager : IUserManager, callback : (error : Error, user : IUser) => void) + { + const onError = (error : Error) => + { + userManager.getDefaultUser((defaultUser) => { + callback(error, defaultUser) + }) + } + + let authHeader = arg.findHeader('Authorization') + if(!authHeader) + { + onError(Errors.MissingAuthorisationHeader) + return; + } + if(!/^Digest (\s*[a-zA-Z]+\s*=\s*"(\\"|[^"])+"\s*(,|$))+$/.test(authHeader)) + { + onError(Errors.WrongHeaderFormat); + return; + } + + authHeader = authHeader.substring(authHeader.indexOf(' ') + 1); // remove the authentication type from the string + + const authProps : any = { }; + + const rex = /([a-zA-Z]+)\s*=\s*"((?:\\"|[^"])+)"/g; + let match = rex.exec(authHeader); + while(match) + { + authProps[match[1]] = match[2]; + match = rex.exec(authHeader); + } + + if(!(authProps.username && authProps.nonce && authProps.nc && authProps.cnonce && authProps.qop && authProps.response)) + { + onError(Errors.AuenticationPropertyMissing); + return; + } + + userManager.getUserByName(authProps.username, (e, user) => { + if(e) + { + onError(e); + return; + } + + const ha1 = md5(authProps.username + ":" + this.realm + ":" + user.password ? user.password : ''); + const ha2 = md5(arg.request.method.toString().toUpperCase() + ":" + arg.uri); + const result = md5(ha1 + ":" + authProps.nonce + ":" + authProps.nc + ":" + authProps.cnonce + ":" + authProps.qop + ":" + ha2); + + if(result.toLowerCase() === authProps.response.toLowerCase()) + callback(Errors.None, user); + else + onError(Errors.BadAuthentication); + }); + } +} diff --git a/src/user/privilege/FakePrivilegeManager.ts b/src/user/privilege/FakePrivilegeManager.ts new file mode 100644 index 00000000..e58d4160 --- /dev/null +++ b/src/user/privilege/FakePrivilegeManager.ts @@ -0,0 +1,26 @@ +import { SimplePrivilegeManager } from './SimplePrivilegeManager' +import { MethodCallArgs } from '../../server/MethodCallArgs' +import { hasNoWriteLock } from './IPrivilegeManager' +import { IResource } from '../../resource/IResource' +import { LockType } from '../../resource/lock/LockType' + +export class FakePrivilegeManager extends SimplePrivilegeManager +{ + constructor() + { + super(); + } + + canCreate = (arg, resource, callback) => callback(null, true) + canDelete = hasNoWriteLock + canWrite = hasNoWriteLock + canRead = (arg, resource, callback) => callback(null, true) + canListLocks = (arg, resource, callback) => callback(null, true) + canSetLock = hasNoWriteLock + canGetAvailableLocks = (arg, resource, callback) => callback(null, true) + canAddChild = hasNoWriteLock + canRemoveChild = hasNoWriteLock + canGetChildren = (arg, resource, callback) => callback(null, true) + canSetProperty = hasNoWriteLock + canGetProperty = (arg, resource, callback) => callback(null, true) +} diff --git a/src/user/privilege/IPrivilegeManager.ts b/src/user/privilege/IPrivilegeManager.ts new file mode 100644 index 00000000..7484f3e4 --- /dev/null +++ b/src/user/privilege/IPrivilegeManager.ts @@ -0,0 +1,85 @@ +import { MethodCallArgs } from '../../server/MethodCallArgs' +import { IResource } from '../../resource/IResource' +import { LockType } from '../../resource/lock/LockType' + +export type PrivilegeManagerCallback = (error : Error, hasAccess : boolean) => void; +export type PrivilegeManagerMethod = (arg : MethodCallArgs, resource : IResource, callback : PrivilegeManagerCallback) => void + +export type BasicPrivilege = + 'canCreate' + | 'canDelete' + | 'canMove' + | 'canRename' + | 'canAppend' + | 'canWrite' + | 'canRead' + | 'canGetMimeType' + | 'canGetSize' + | 'canListLocks' + | 'canSetLock' + | 'canRemoveLock' + | 'canGetAvailableLocks' + | 'canGetLock' + | 'canAddChild' + | 'canRemoveChild' + | 'canGetChildren' + | 'canSetProperty' + | 'canGetProperty' + | 'canGetProperties' + | 'canRemoveProperty' + | 'canGetCreationDate' + | 'canGetLastModifiedDate' + | 'canGetWebName' + | 'canGetType'; + +export function requirePrivilege(privilege : string | BasicPrivilege | string[] | BasicPrivilege[], arg : MethodCallArgs, resource : IResource, callback : PrivilegeManagerCallback) +{ + const privileges : string[] = privilege.constructor !== Array ? [ privilege as string ] : privilege as string[]; + const pm = arg.server.privilegeManager; + + go(); + function go(error : Error = null, hasAccess : boolean = true) + { + if(privileges.length === 0 || error || !hasAccess) + { + callback(error, hasAccess); + return; + } + + pm[privileges.shift()](arg, resource, go); + } +} + +export interface IPrivilegeManager +{ + canCreate : PrivilegeManagerMethod + canDelete : PrivilegeManagerMethod + canMove : PrivilegeManagerMethod + canRename : PrivilegeManagerMethod + canAppend : PrivilegeManagerMethod + canWrite : PrivilegeManagerMethod + canRead : PrivilegeManagerMethod + canGetMimeType : PrivilegeManagerMethod + canGetSize : PrivilegeManagerMethod + canListLocks : PrivilegeManagerMethod + canSetLock : PrivilegeManagerMethod + canRemoveLock : PrivilegeManagerMethod + canGetAvailableLocks : PrivilegeManagerMethod + canGetLock : PrivilegeManagerMethod + canAddChild : PrivilegeManagerMethod + canRemoveChild : PrivilegeManagerMethod + canGetChildren : PrivilegeManagerMethod + canSetProperty : PrivilegeManagerMethod + canGetProperty : PrivilegeManagerMethod + canGetProperties : PrivilegeManagerMethod + canRemoveProperty : PrivilegeManagerMethod + canGetCreationDate : PrivilegeManagerMethod + canGetLastModifiedDate : PrivilegeManagerMethod + canGetWebName : PrivilegeManagerMethod + canGetType : PrivilegeManagerMethod +} + +export function hasNoWriteLock(arg : MethodCallArgs, resource : IResource, callback : PrivilegeManagerCallback) +{ + resource.getLocks((e, locks) => callback(e, locks ? locks.filter((l) => l.user !== arg.user && l.lockKind.type.isSame(LockType.Write)).length === 0 : false)); +} diff --git a/src/user/privilege/SimplePrivilegeManager.ts b/src/user/privilege/SimplePrivilegeManager.ts new file mode 100644 index 00000000..7e0cfa3f --- /dev/null +++ b/src/user/privilege/SimplePrivilegeManager.ts @@ -0,0 +1,41 @@ +import { PrivilegeManagerMethod } from './IPrivilegeManager' +import { IPrivilegeManager } from './IPrivilegeManager' +import { MethodCallArgs } from '../../server/MethodCallArgs' +import { IResource } from '../../resource/IResource' +import { LockType } from '../../resource/lock/LockType' + +export abstract class SimplePrivilegeManager implements IPrivilegeManager +{ + abstract canCreate : PrivilegeManagerMethod + abstract canDelete : PrivilegeManagerMethod + canMove = (arg, resource, callback) => { + this.canDelete(arg, resource, (e, v) => { + if(e || !v) + callback(e, v); + else + this.canRead(arg, resource, callback); + }) + } + canRename = (arg, resource, callback) => this.canWrite(arg, resource, callback) + canAppend = (arg, resource, callback) => this.canWrite(arg, resource, callback) + abstract canWrite : PrivilegeManagerMethod + abstract canRead : PrivilegeManagerMethod + canGetMimeType = (arg, resource, callback) => this.canRead(arg, resource, callback) + canGetSize = (arg, resource, callback) => this.canRead(arg, resource, callback) + abstract canListLocks : PrivilegeManagerMethod + abstract canSetLock : PrivilegeManagerMethod + canRemoveLock = (arg, resource, callback) => this.canSetLock(arg, resource, callback) + abstract canGetAvailableLocks : PrivilegeManagerMethod + canGetLock = (arg, resource, callback) => this.canListLocks(arg, resource, callback) + abstract canAddChild : PrivilegeManagerMethod + abstract canRemoveChild : PrivilegeManagerMethod + abstract canGetChildren : PrivilegeManagerMethod + abstract canSetProperty : PrivilegeManagerMethod + abstract canGetProperty : PrivilegeManagerMethod + canGetProperties = (arg, resource, callback) => this.canGetProperty(arg, resource, callback) + canRemoveProperty = (arg, resource, callback) => this.canSetProperty(arg, resource, callback) + canGetCreationDate = (arg, resource, callback) => this.canRead(arg, resource, callback) + canGetLastModifiedDate = (arg, resource, callback) => this.canRead(arg, resource, callback) + canGetWebName = (arg, resource, callback) => this.canRead(arg, resource, callback) + canGetType = (arg, resource, callback) => this.canRead(arg, resource, callback) +} diff --git a/src/user/simple/SimpleUser.ts b/src/user/simple/SimpleUser.ts new file mode 100644 index 00000000..cd3c29ae --- /dev/null +++ b/src/user/simple/SimpleUser.ts @@ -0,0 +1,11 @@ +import { IUser } from '../IUser' + +export class SimpleUser implements IUser +{ + constructor( + public username : string, + public password : string, + public isAdministrator : boolean, + public isDefaultUser : boolean + ) { } +} diff --git a/src/user/simple/SimpleUserManager.ts b/src/user/simple/SimpleUserManager.ts new file mode 100644 index 00000000..953f29fc --- /dev/null +++ b/src/user/simple/SimpleUserManager.ts @@ -0,0 +1,42 @@ +import { IUserManager } from '../IUserManager' +import { SimpleUser } from './SimpleUser' +import { IUser } from '../IUser' + +export class SimpleUserManager implements IUserManager +{ + protected users : any + + constructor() + { + this.users = { + __default: new SimpleUser('DefaultUser', null, false, true) + }; + } + + getUserByName(name : string, callback : (error : Error, user : IUser) => void) + { + if(!this.users[name]) + callback(new Error('User not found'), null); + else + callback(null, this.users[name]); + } + getDefaultUser(callback : (user : IUser) => void) + { + callback(this.users.__default); + } + + addUser(name : string, password : string, isAdmin : boolean = false) + { + this.users[name] = new SimpleUser(name, password, isAdmin, false); + } + + getUsers(callback : (error : Error, users : IUser[]) => void) + { + const users = []; + + for(const name in this.users) + users.push(this.users[name]); + + callback(null, users); + } +}