Skip to content

Commit

Permalink
Add support for logging in to SciServer directly. Closes #1999
Browse files Browse the repository at this point in the history
  • Loading branch information
brollb committed Mar 23, 2021
1 parent 7dbfa78 commit 09d96b0
Show file tree
Hide file tree
Showing 12 changed files with 141 additions and 101 deletions.
3 changes: 2 additions & 1 deletion src/common/compute/backends/ComputeClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
define([], function() {

class ComputeClient {
constructor (logger, blobClient) {
constructor (logger, blobClient, config) {
this.logger = logger.fork('compute');
this.userId = config.userId;
this.blobClient = blobClient;
this._events = {};
}
Expand Down
9 changes: 3 additions & 6 deletions src/common/compute/backends/sciserver-compute/Client.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ define([
module,
path,
fetch,
login,
getToken,
PREPARE_AND_RUN,
) {
const Headers = fetch.Headers;
Expand All @@ -26,7 +26,6 @@ define([
constructor(logger, blobClient, config) {
super(logger, blobClient, config);
this.username = config.username;
this.password = config.password;
this.computeDomain = config.computeDomain;
this.previousJobState = {};
this.consoleOutputLen = {};
Expand Down Expand Up @@ -66,7 +65,6 @@ define([
const metadata = await this.blobClient.getMetadata(hash);
const config = {
username: this.username,
password: this.password,
volume: `${this.username}/scratch`,
volumePool: 'Temporary'
};
Expand Down Expand Up @@ -117,7 +115,7 @@ define([
}

async token () {
return login(this.username, this.password);
return getToken(this.username, this.userId);
}

async getJobState (jobInfo) {
Expand Down Expand Up @@ -234,11 +232,10 @@ define([
_getStorageConfigAndDataInfo (filepath) {
const dirs = filepath.split('/').slice(4);
let [volumePool, owner, volume] = dirs.slice(0, 3);
const password = this.password;
volume = owner + '/' + volume;
const filename = dirs.slice(3).join('/');
return {
config: {username: this.username, volumePool, password, volume},
config: {username: this.username, volumePool, volume},
dataInfo: {
data: {filename, volume, volumePool}
}
Expand Down
22 changes: 10 additions & 12 deletions src/common/compute/backends/sciserver-compute/metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,17 @@
"displayName": "Username",
"description": "SciServer username",
"value": "",
"valueType": "string",
"readOnly": false
},
{
"name": "password",
"displayName": "Password",
"description": "SciServer password",
"value": "",
"valueType": "string",
"valueType": "stringX",
"valueItemsURL": "/routers/SciServerAuth",
"extraValueItems": [
{
"name": "Link account...",
"type": "URL",
"value": "https://apps.sciserver.org/login-portal/login?callbackUrl=<%= window.location.origin %>/routers/SciServerAuth/register"
}
],
"readOnly": false,
"options": {
"isPassword": true
}
"isAuth": true
},
{
"name": "computeDomain",
Expand Down
74 changes: 17 additions & 57 deletions src/common/sciserver-auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,70 +10,30 @@
root.CONSTANTS = factory();
}
}(this, function() {
const LOGIN_URL = 'https://apps.sciserver.org/login-portal/keystone/v3/tokens';
const isBrowser = typeof window !== 'undefined';
const fetch = isBrowser ? window.fetch : require('node-fetch');
const Headers = isBrowser ? window.Headers : fetch.Headers;
const TokenStorage = isBrowser ? null : require('../routers/SciServerAuth/Tokens');

async function loginViaProxy(username, password) {
const url = '/routers/SciServerAuth/token';
const opts = {
method: 'POST',
headers: new Headers({
'Content-Type': 'application/json'
}),
body: JSON.stringify({username, password})
};
const response = await fetch(url, opts);
return await response.text();
}

async function fetchNewToken(username, password) {
if (isBrowser) {
return loginViaProxy(username, password);
}

const url = `${LOGIN_URL}?TaskName=DeepForge.Authentication.Login`;
const opts = {
method: 'POST',
headers: new Headers({
'Content-Type': 'application/json'
}),
body: getLoginBody(username, password)
};
const response = await fetch(url, opts);
return response.headers.get('X-Subject-Token');
}

const tokens = {};
const hours = 1000*60*60;
function login(username, password) {
tokens[username] = tokens[username] || {};
if (!tokens[username][password]) {
tokens[username][password] = fetchNewToken(username, password);
setTimeout(clearToken.bind(null, username, password), 23*hours);
async function getTokenBrowser(ssUser) {
const url = `/routers/SciServerAuth/${ssUser}/token`;
const response = await fetch(url);
if (response.status < 400) {
return await response.text();
} else {
throw new Error(await response.text());
}
return tokens[username][password];
}

function clearToken(username, password) {
delete tokens[username][password];
async function getTokenNodeJS(ssUser, dfUser) {
return await TokenStorage.getToken(dfUser, ssUser);
}

function getLoginBody(username, password) {
return JSON.stringify({
auth: {
identity: {
password: {
user: {
name: username,
password: password
}
}
}
}
});
async function getToken(ssUser, dfUser) {
if (isBrowser) {
return getTokenBrowser(ssUser);
} else {
return getTokenNodeJS(ssUser, dfUser);
}
}

return login;
return getToken;
}));
3 changes: 2 additions & 1 deletion src/common/storage/backends/StorageClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ define([
require.nodeRequire('node-fetch');
const Headers = require.isBrowser ? window.Headers : fetch.Headers;
const stream = require.isBrowser ? null : require.nodeRequire('stream');
const StorageClient = function(id, name, logger) {
const StorageClient = function(id, name, logger, config) {
this.id = id;
this.name = name;
this.userId = config.userId;
if (!logger) {
logger = Logger.create(`gme:storage:${id}`, gmeConfig.client.log);
}
Expand Down
5 changes: 2 additions & 3 deletions src/common/storage/backends/sciserver-files/Client.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ define([
'deepforge/sciserver-auth',
], function (
StorageClient,
login,
getToken,
) {
const BASE_URL = 'https://apps.sciserver.org/fileservice/api/';
const SciServerFiles = function (id, name, logger, config = {}) {
StorageClient.apply(this, arguments);
this.username = config.username;
this.password = config.password;
this.volumePool = config.volumePool || 'Storage';
this.volume = (config.volume || '').replace(/^Storage\//, '');
};
Expand Down Expand Up @@ -84,7 +83,7 @@ define([
};

SciServerFiles.prototype.fetch = async function (action, url, opts = {}) {
const token = await login(this.username, this.password);
const token = await getToken(this.username, this.userId);
opts.headers = opts.headers || {};
opts.headers['X-Auth-Token'] = token;
try {
Expand Down
24 changes: 10 additions & 14 deletions src/common/storage/backends/sciserver-files/metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,20 @@
{
"name": "username",
"displayName": "Username",
"description": "SciServer username",
"description": "SciServer account to use",
"value": "",
"valueType": "string",
"valueType": "stringX",
"valueItemsURL": "/routers/SciServerAuth",
"extraValueItems": [
{
"name": "Link account...",
"type": "URL",
"value": "https://apps.sciserver.org/login-portal/login?callbackUrl=<%= window.location.origin %>/routers/SciServerAuth/register"
}
],
"readOnly": false,
"isAuth": true
},
{
"name": "password",
"displayName": "Password",
"description": "SciServer password",
"value": "",
"valueType": "string",
"readOnly": false,
"isAuth": true,
"options": {
"isPassword": true
}
},
{
"name": "volume",
"displayName": "Volume",
Expand Down
3 changes: 3 additions & 0 deletions src/plugins/ExecuteJob/ExecuteJob.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ define([
ExecuteJob.prototype.createComputeClient = function () {
const config = this.getCurrentConfig();
const backend = Compute.getBackend(config.compute.id);
config.compute.config.userId = this.getUserId();
if (config.compute.id === 'gme') {
config.compute.config.webgmeToken = this.blobClient.webgmeToken; // HACK
}
Expand Down Expand Up @@ -218,6 +219,7 @@ define([
ExecuteJob.prototype.getStorageClient = async function () {
const {storage} = this.getCurrentConfig();
const backend = Storage.getBackend(storage.id);
storage.config.userId = this.getUserId();
return await backend.getClient(this.logger, storage.config);
};

Expand All @@ -235,6 +237,7 @@ define([
ExecuteJob.prototype.getStorageClientForInputData = async function (dataInfo) {
const configDict = await this.getInputStorageConfigs();
const config = configDict[JSON.stringify(dataInfo)];
config.userId = this.getUserId();
const client = await Storage.getClient(dataInfo.backend, null, config);
return client;
};
Expand Down
1 change: 1 addition & 0 deletions src/plugins/ImportArtifact/ImportArtifact.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ define([

ImportArtifact.prototype.symLink = async function(path, storage) {
const {id, config} = storage;
config.userId = this.getUserId();
const srcStorage = await Storage.getBackend(id).getClient(this.logger, config);
return await srcStorage.stat(path);
};
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/ImportArtifact/metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"class": "glyphicon glyphicon-transfer",
"src": ""
},
"disableServerSideExecution": false,
"disableServerSideExecution": true,
"disableBrowserSideExecution": false,
"writeAccessRequired": true,
"configStructure": [
Expand Down
48 changes: 42 additions & 6 deletions src/routers/SciServerAuth/SciServerAuth.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

const express = require('express');
const router = express.Router();
const login = require('../../common/sciserver-auth');
const fetch = require('node-fetch');
const TokenStorage = require('./tokens');
let gmeConfig;
let storage;

/**
* Called when the server is created but before it starts to listening to incoming requests.
Expand All @@ -19,28 +22,61 @@ const login = require('../../common/sciserver-auth');
* @param {object} middlewareOpts.workerManager - Spawns and keeps track of "worker" sub-processes.
*/
function initialize(middlewareOpts) {
gmeConfig = middlewareOpts.gmeConfig;
const getUserId = req => {
return middlewareOpts.getUserId(req) || gmeConfig.authentication.guestAccount;
};
const logger = middlewareOpts.logger.fork('SciServerAuth');
storage = require('../storage')(logger, gmeConfig);

logger.debug('initializing ...');

router.post('/token', async function (req, res/*, next*/) {
const {username, password} = req.body;
router.get('/register', async function (req, res) {
const {token} = req.query;
const userId = getUserId(req);
try {
const token = await login(username, password);
res.status(200).send(token);
const name = await getUsername(token);
await TokenStorage.register(userId, name, token);
res.status(200).send(`SciServer account "${name}" is now accessible from DeepForge for the next 24 hours.`);
} catch (err) {
res.status(500).send(err.message);
}
});

router.get('/', async function (req, res) {
const userId = getUserId(req);
const names = await TokenStorage.getUsernames(userId);
return res.json(names);
});

router.get('/:name/token', async function (req, res) {
const userId = getUserId(req);
const {name} = req.params;
try {
return res.send(await TokenStorage.getToken(userId, name));
} catch (err) {
const statusCode = err instanceof TokenStorage.NotFoundError ? 404 : 500;
return res.status(statusCode).send(err.message);
}
});

logger.debug('ready');
}

async function getUsername(token) {
const url = `https://apps.sciserver.org/login-portal/keystone/v3/tokens/${token}`;
const response = await fetch(url);
const data = await response.json();
return data.token.user.name;
}

/**
* Called before the server starts listening.
* @param {function} callback
*/
function start(callback) {
async function start(callback) {
const db = await storage;
TokenStorage.init(gmeConfig, db);
callback();
}

Expand Down
Loading

0 comments on commit 09d96b0

Please sign in to comment.