Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GPII-3496: Auto-login with user folder #839

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions gpii/node_modules/userListeners/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ fluid.module.register("userListeners", __dirname, require);
require("./src/listeners.js");
require("./src/pcsc.js");
require("./src/usb.js");
require("./src/userFolder.js");
98 changes: 97 additions & 1 deletion gpii/node_modules/userListeners/src/listeners.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@

"use strict";

var fluid = require("infusion");
var fluid = require("infusion"),
fs = require("fs"),
path = require("path");

var gpii = fluid.registerNamespace("gpii");

require("../../lifecycleManager/");

fluid.registerNamespace("gpii.userListeners");

// The user listeners.
Expand Down Expand Up @@ -55,6 +59,15 @@ fluid.defaults("gpii.userListeners", {
onListenersStop: "{userListeners}.events.onListenersStop"
}
}
},
userFolder: {
type: "gpii.userListeners.userFolder",
options: {
events: {
onListenersStart: "{userListeners}.events.onListenersStart",
onListenersStop: "{userListeners}.events.onListenersStop"
}
}
}
},
events: {
Expand Down Expand Up @@ -106,6 +119,14 @@ fluid.defaults("gpii.userListener", {
"{that}",
"{arguments}.0" // The error.
]
},
readTokenFile: {
funcName: "gpii.userListeners.readTokenFile",
args: [ "{arguments}.0", "{arguments}.1" ] // directory, file
},
writeTokenFile: {
funcName: "gpii.userListeners.writeTokenFile",
args: [ "{arguments}.0", "{arguments}.1", "{arguments}.2" ] // directory, file, token
}
},
members: {
Expand Down Expand Up @@ -208,3 +229,78 @@ gpii.userListeners.failed = function (that, err) {
});
});
};

/**
* Reads a token from the file.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please replace the use of "token" with "GPII key". Thanks.

*
* @param {String} tokenDirectory - The directory containing the token file.
* @param {String} tokenFile [optional] The file, in tokenDirectory, containing the token. Can be omitted if the first
* argument is the full path to the file.
* @return {Promise} resolves when the token is read, rejects if there's no file (or there was an error reading it).
*/
gpii.userListeners.readTokenFile = function (tokenDirectory, tokenFile) {
var promise = fluid.promise();

// Try to read the token file.
var tokenPath = path.join(tokenDirectory, tokenFile || "");
fluid.log("Reading token file: ", tokenPath);
fs.readFile(tokenPath, "utf8", function (err, data) {
if (err) {
if (err.code === "ENOENT") {
fluid.log("Token file not found");
promise.reject();
} else {
var message = "Error reading token file " + tokenPath + ": " + err.message;
fluid.log(message);
promise.reject({
isError: true,
message: message,
error: err
});
}
} else {
var token = data.trim();
if (token.length > 0) {
fluid.log("Got token: ", token);
promise.resolve(token);
} else {
fluid.log("Empty token file");
promise.reject();
}
}
});

return promise;
};

/**
* Writes a token to a file.
*
* @param {String} tokenDirectory - The directory containing the token file.
* @param {String} tokenFile [optional] The file, in tokenDirectory, containing the token. Can be omitted if the first
* argument is the full path to the file.
* @param {String} tokenValue - The token's value.
* @return {Promise} resolves when complete, with a value of true if it was written.
*/
gpii.userListeners.writeTokenFile = function (tokenDirectory, tokenFile, tokenValue) {
var promise = fluid.promise();

var tokenPath = path.join(tokenDirectory, tokenFile || "");
fluid.log("Writing token '" + tokenValue + "' to ", tokenPath);

fs.writeFile(tokenPath, tokenValue, "utf8", function (err) {
if (err) {
var message = "Error writing token file " + tokenPath + ": " + err.message;
fluid.log(message);
promise.reject({
isError: true,
message: message,
error: err
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the convention, the path for the original error object is named as "originalError" rather than "error".

});
} else {
promise.resolve(true);
}
});

return promise;
};
29 changes: 5 additions & 24 deletions gpii/node_modules/userListeners/src/usb.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@

"use strict";

var fluid = require("infusion"),
fs = require("fs"),
path = require("path");
var fluid = require("infusion");

var gpii = fluid.registerNamespace("gpii");

Expand Down Expand Up @@ -84,27 +82,10 @@ gpii.userListeners.usb.readUSBToken = function (that, usbPath) {
var promise = fluid.promise();

// Try to read the token file.
var tokenFile = path.join(usbPath, that.tokenFile);
fs.readFile(tokenFile, "utf8", function (err, data) {
if (err) {
fluid.log("Error reading token from device (" + usbPath + "):");
promise.reject({
isError: true,
message: "Error reading token from device (" + usbPath + ")",
error: err
});
} else {
that.currentDevice.path = usbPath;
fluid.log("Got token from USB device (" + that.currentDevice.path + "):", that.currentDevice.token);
if (that.currentDevice.token !== data.trim()) {
that.currentDevice.token = data.trim();
that.events.onTokenArrive.fire(that, that.currentDevice.token);
} else {
fluid.log("Ignoring key-in request. Got the same token from a different USB device (" + that.currentDevice.path + "):",
that.currentDevice.token);
}
promise.resolve(that.currentDevice.token);
}
promise = that.readTokenFile(usbPath, that.tokenFile).then(function (token) {
that.currentDevice.token = token;
that.currentDevice.path = usbPath;
that.events.onTokenArrive.fire(that, that.currentDevice.token);
});

return promise;
Expand Down
115 changes: 115 additions & 0 deletions gpii/node_modules/userListeners/src/userFolder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/* userFolder user listener.
*
* Copyright 2017 Raising the Floor - International
*
* Licensed under the New BSD license. You may not use this file except in
* compliance with this License.
*
* The R&D leading to these results received funding from the
* Department of Education - Grant H421A150005 (GPII-APCP). However,
* these results do not necessarily represent the policy of the
* Department of Education, and you should not assume endorsement by the
* Federal Government.
*
* You may obtain a copy of the License at
* https://github.com/GPII/universal/blob/master/LICENSE.txt
*/

"use strict";

var fluid = require("infusion");

var gpii = fluid.registerNamespace("gpii");

// The user folder user listener.
fluid.defaults("gpii.userListeners.userFolder", {
gradeNames: ["fluid.component", "fluid.contextAware", "gpii.userListener"],
contextAwareness: {
platform: {
checks: {
windows: {
contextValue: "{gpii.contexts.windows}",
gradeNames: "gpii.userListeners.userFolder.windows"
}
}
}
},
members: {
tokenDirectory: "@expand:{settingsDir}.getGpiiSettingsDir()",
tokenFile: ".gpii-user-token.txt",
done: false,
listenerName: "userFolder"
},
components: {
settingsDir: {
type: "gpii.settingsDir"
}
},
invokers: {
startListener: "{that}.attemptLogin",
stopListener: "fluid.identity",
attemptLogin: {
funcName: "gpii.userListeners.userFolder.attemptLogin",
args: ["{that}"]
},
getToken: {
func: "{that}.readTokenFile",
args: ["{that}.tokenDirectory", "{that}.tokenFile"]
},
storeToken: {
func: "{that}.writeTokenFile",
args: [ "{that}.tokenDirectory", "{that}.tokenFile", "{arguments}.0" ]
}
},
listeners: {
"onCreate.loginListener": {
funcName: "gpii.userListeners.userFolder.addLoginListener",
args: [ "{that}", "{lifecycleManager}.events.onSessionStart"]
}
},
// Disabling by default, as it is the safest and will prevent people's keys from being accidentally stored when it
// is not desired (eg, on shared or public access computers).
saveLastLogin: false
});

/**
* Adds a listener to the onSessionStart event of the lifecycle manager, which stores the token file.
*
* @param {Component} that The gpii.userListeners.userFolder instance.
* @param {EventFirer} firer The event firer for the onSessionStart event.
*/
gpii.userListeners.userFolder.addLoginListener = function (that, firer) {
if (that.options.saveLastLogin) {
firer.addListener(function (gradeName, gpiiKey) {
if (gpiiKey !== "noUser") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might like to check the gpiiKey is not any of reserved keys. A list of reserved keys can be found [here|https://github.com/GPII/universal/blob/master/gpii/node_modules/flowManager/src/SystemUtils.js#L19|.

that.storeToken(gpiiKey);
}
});
}
};

/**
* Performs the key-in, by raising the onTokenArrive event with the result of getToken().
*
* This should only be called once, during startup. Subsequent calls will have no effect.
*
* @param {Component} that - The gpii.userListeners.userFolder instance.
* @return {Promise} A promise resolving when the token has been read.
*/
gpii.userListeners.userFolder.attemptLogin = function (that) {
var promise;

if (that.done) {
promise = fluid.promise().reject({
isError: true,
message: "userFolder.attemptLogin had already been called."
});
} else {
that.done = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall this line be inside the promise.then() block down below so that in the case that.getToken() fails, that.done will not be set to true.

promise = that.getToken().then(function (token) {
that.events.onTokenArrive.fire(that, token);
});
}

return promise;
};
1 change: 1 addition & 0 deletions gpii/node_modules/userListeners/test/all-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@

require("./pcscTests.js");
require("./usbTests.js");
require("./userFolderTests.js");
22 changes: 22 additions & 0 deletions gpii/node_modules/userListeners/test/pcscTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,28 @@ fluid.defaults("gpii.test.userListeners.pcsc", {
}
});

/*
* Get an instance of the gpii.userListeners.pcsc component.
*/
gpii.tests.userListener.getPcscListener = function () {
var userListeners = gpii.userListeners({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did these new global functions need to get added for each test function?
Note that all of this component tree will leak between tests. I'm not sure I understand what is going on here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also.... nothing calls this function?

listeners: {
"onCreate.startListeners": "fluid.identity"
},
events: {
"onListenersStart": null
},
distributeOptions: {
pcsc: {
record: "gpii.test.userListeners.pcsc",
target: "{that pcsc}.options.gradeNames"
}
}
});

return userListeners.pcsc;
};

/**
* Creates a card reader that emulates a real reader, by returning a canned response to commands.
*
Expand Down
24 changes: 23 additions & 1 deletion gpii/node_modules/userListeners/test/usbTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,28 @@ jqUnit.module("gpii.tests.userListener.usb", {
}
});

/*
* Get an instance of the gpii.userListeners.usb component.
*/
gpii.tests.userListener.getUSBListener = function () {
var userListeners = gpii.userListeners({
listeners: {
"onCreate.startListeners": "fluid.identity"
},
events: {
"onListenersStart": null
},
distributeOptions: {
usb: {
record: "gpii.tests.userListener.usbListener",
target: "{that usb}.options.gradeNames"
}
}
});

return userListeners.usb;
};

gpii.tests.userListener.insertTests = [
// Device added
{
Expand Down Expand Up @@ -189,7 +211,7 @@ gpii.tests.userListener.deviceMountTests = function (tests, func, createFiles, e
// Tests USB device insertion.
jqUnit.asyncTest("USB listener - device inserted", function () {
gpii.tests.userListener.deviceMountTests(
gpii.tests.userListener.insertTests, gpii.userListeners.usb.readUSBToken, true, 9);
gpii.tests.userListener.insertTests, gpii.userListeners.usb.readUSBToken, true, 10);
});

// Tests USB device removal.
Expand Down
Loading