2017-01-31 07:50:28 +01:00
|
|
|
const Backbone = require('backbone');
|
|
|
|
const Logger = require('../util/logger');
|
|
|
|
const AppSettingsModel = require('../models/app-settings-model');
|
|
|
|
const RuntimeDataModel = require('../models/runtime-data-model');
|
|
|
|
const Links = require('../const/links');
|
2017-04-16 18:05:58 +02:00
|
|
|
const FeatureDetector = require('../util/feature-detector');
|
2016-03-27 09:06:23 +02:00
|
|
|
|
2017-01-31 07:50:28 +01:00
|
|
|
const MaxRequestRetries = 3;
|
2016-03-27 15:18:05 +02:00
|
|
|
|
2019-08-16 23:05:39 +02:00
|
|
|
const StorageBase = function() {};
|
2016-03-27 09:06:23 +02:00
|
|
|
|
|
|
|
_.extend(StorageBase.prototype, {
|
|
|
|
name: null,
|
|
|
|
icon: null,
|
|
|
|
iconSvg: null,
|
|
|
|
enabled: false,
|
|
|
|
system: false,
|
|
|
|
uipos: null,
|
|
|
|
|
|
|
|
logger: null,
|
|
|
|
appSettings: AppSettingsModel.instance,
|
2016-03-27 14:57:22 +02:00
|
|
|
runtimeData: RuntimeDataModel.instance,
|
2016-03-27 09:06:23 +02:00
|
|
|
|
|
|
|
init: function() {
|
|
|
|
if (!this.name) {
|
|
|
|
throw 'Failed to init provider: no name';
|
|
|
|
}
|
|
|
|
if (!this.system) {
|
2017-01-31 07:50:28 +01:00
|
|
|
const enabled = this.appSettings.get(this.name);
|
2016-03-27 09:06:23 +02:00
|
|
|
if (typeof enabled === 'boolean') {
|
|
|
|
this.enabled = enabled;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.logger = new Logger('storage-' + this.name);
|
2017-04-16 18:05:58 +02:00
|
|
|
if (this._oauthReturnMessage) {
|
|
|
|
this.logger.debug('OAuth return message', this._oauthReturnMessage);
|
2017-12-09 21:46:08 +01:00
|
|
|
this._oauthProcessReturn(this._oauthReturnMessage);
|
2017-04-16 18:05:58 +02:00
|
|
|
delete this._oauthReturnMessage;
|
|
|
|
delete sessionStorage.authStorage;
|
2019-03-28 22:02:04 +01:00
|
|
|
if (FeatureDetector.isStandalone) {
|
|
|
|
const [url, urlParams] = location.href.split(/[?#]/);
|
|
|
|
if (urlParams) {
|
|
|
|
location.href = url;
|
|
|
|
}
|
|
|
|
}
|
2017-04-16 18:05:58 +02:00
|
|
|
}
|
2016-03-27 09:46:43 +02:00
|
|
|
return this;
|
2016-03-27 09:42:48 +02:00
|
|
|
},
|
|
|
|
|
2016-06-04 17:08:50 +02:00
|
|
|
setEnabled: function(enabled) {
|
|
|
|
this.enabled = enabled;
|
|
|
|
},
|
|
|
|
|
2017-04-16 18:05:58 +02:00
|
|
|
handleOAuthReturnMessage(message) {
|
|
|
|
this._oauthReturnMessage = message;
|
|
|
|
},
|
|
|
|
|
2016-03-27 09:42:48 +02:00
|
|
|
_xhr: function(config) {
|
2017-01-31 07:50:28 +01:00
|
|
|
const xhr = new XMLHttpRequest();
|
2016-03-27 09:42:48 +02:00
|
|
|
if (config.responseType) {
|
|
|
|
xhr.responseType = config.responseType;
|
|
|
|
}
|
2017-01-31 07:50:28 +01:00
|
|
|
const statuses = config.statuses || [200];
|
2016-07-17 13:30:38 +02:00
|
|
|
xhr.addEventListener('load', () => {
|
2016-03-27 15:18:05 +02:00
|
|
|
if (statuses.indexOf(xhr.status) >= 0) {
|
|
|
|
return config.success && config.success(xhr.response, xhr);
|
|
|
|
}
|
2016-07-17 13:30:38 +02:00
|
|
|
if (xhr.status === 401 && this._oauthToken) {
|
|
|
|
this._oauthRefreshToken(err => {
|
2016-03-27 15:18:05 +02:00
|
|
|
if (err) {
|
|
|
|
return config.error && config.error('unauthorized', xhr);
|
|
|
|
} else {
|
|
|
|
config.tryNum = (config.tryNum || 0) + 1;
|
|
|
|
if (config.tryNum >= MaxRequestRetries) {
|
2019-08-18 08:05:38 +02:00
|
|
|
this.logger.info(
|
|
|
|
'Too many authorize attempts, fail request',
|
|
|
|
config.url
|
|
|
|
);
|
2016-03-27 15:18:05 +02:00
|
|
|
return config.error && config.error('unauthorized', xhr);
|
|
|
|
}
|
2016-07-17 13:30:38 +02:00
|
|
|
this.logger.info('Repeat request, try #' + config.tryNum, config.url);
|
|
|
|
this._xhr(config);
|
2016-03-27 15:18:05 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
} else {
|
2016-03-27 09:42:48 +02:00
|
|
|
return config.error && config.error('http status ' + xhr.status, xhr);
|
|
|
|
}
|
|
|
|
});
|
2016-07-17 13:30:38 +02:00
|
|
|
xhr.addEventListener('error', () => {
|
2016-03-27 15:18:05 +02:00
|
|
|
return config.error && config.error('network error', xhr);
|
2016-03-27 09:42:48 +02:00
|
|
|
});
|
2016-07-17 13:30:38 +02:00
|
|
|
xhr.addEventListener('timeout', () => {
|
2016-03-27 15:18:05 +02:00
|
|
|
return config.error && config.error('timeout', xhr);
|
2016-03-27 09:42:48 +02:00
|
|
|
});
|
|
|
|
xhr.open(config.method || 'GET', config.url);
|
2018-03-13 19:17:16 +01:00
|
|
|
if (this._oauthToken && !config.skipAuth) {
|
2017-04-16 17:00:35 +02:00
|
|
|
xhr.setRequestHeader('Authorization', 'Bearer ' + this._oauthToken.accessToken);
|
2016-03-27 14:57:22 +02:00
|
|
|
}
|
2016-07-17 13:30:38 +02:00
|
|
|
_.forEach(config.headers, (value, key) => {
|
2016-03-27 09:42:48 +02:00
|
|
|
xhr.setRequestHeader(key, value);
|
|
|
|
});
|
|
|
|
xhr.send(config.data);
|
2016-03-27 13:54:35 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
_openPopup: function(url, title, width, height) {
|
2017-01-31 07:50:28 +01:00
|
|
|
const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : screen.left;
|
|
|
|
const dualScreenTop = window.screenTop !== undefined ? window.screenTop : screen.top;
|
2016-03-27 13:54:35 +02:00
|
|
|
|
2019-08-16 23:05:39 +02:00
|
|
|
const winWidth = window.innerWidth
|
|
|
|
? window.innerWidth
|
|
|
|
: document.documentElement.clientWidth
|
|
|
|
? document.documentElement.clientWidth
|
|
|
|
: screen.width;
|
|
|
|
const winHeight = window.innerHeight
|
|
|
|
? window.innerHeight
|
|
|
|
: document.documentElement.clientHeight
|
|
|
|
? document.documentElement.clientHeight
|
|
|
|
: screen.height;
|
2016-03-27 13:54:35 +02:00
|
|
|
|
2019-08-16 23:05:39 +02:00
|
|
|
const left = winWidth / 2 - width / 2 + dualScreenLeft;
|
|
|
|
const top = winHeight / 2 - height / 2 + dualScreenTop;
|
2016-03-27 13:54:35 +02:00
|
|
|
|
2017-01-31 07:50:28 +01:00
|
|
|
let settings = {
|
2016-03-27 13:54:35 +02:00
|
|
|
width: width,
|
|
|
|
height: height,
|
|
|
|
left: left,
|
|
|
|
top: top,
|
|
|
|
dialog: 'yes',
|
|
|
|
dependent: 'yes',
|
|
|
|
scrollbars: 'yes',
|
|
|
|
location: 'yes'
|
|
|
|
};
|
2019-08-16 23:05:39 +02:00
|
|
|
settings = Object.keys(settings)
|
|
|
|
.map(key => key + '=' + settings[key])
|
|
|
|
.join(',');
|
2017-04-16 18:05:58 +02:00
|
|
|
if (FeatureDetector.isStandalone) {
|
|
|
|
sessionStorage.authStorage = this.name;
|
|
|
|
}
|
2016-03-27 13:54:35 +02:00
|
|
|
|
2016-08-14 23:22:25 +02:00
|
|
|
return window.open(url, title, settings);
|
2016-03-27 14:57:22 +02:00
|
|
|
},
|
|
|
|
|
2016-04-03 16:33:34 +02:00
|
|
|
_getOauthRedirectUrl: function() {
|
2017-01-31 07:50:28 +01:00
|
|
|
let redirectUrl = window.location.href;
|
2016-04-03 16:33:34 +02:00
|
|
|
if (redirectUrl.lastIndexOf('file:', 0) === 0) {
|
|
|
|
redirectUrl = Links.WebApp;
|
|
|
|
}
|
2018-03-08 19:37:52 +01:00
|
|
|
redirectUrl = redirectUrl.split('?')[0];
|
2016-04-03 16:33:34 +02:00
|
|
|
return redirectUrl;
|
|
|
|
},
|
|
|
|
|
2016-03-27 15:18:05 +02:00
|
|
|
_oauthAuthorize: function(callback) {
|
2017-11-27 19:00:39 +01:00
|
|
|
if (this._tokenIsValid(this._oauthToken)) {
|
2016-03-27 15:18:05 +02:00
|
|
|
return callback();
|
|
|
|
}
|
2017-01-31 07:50:28 +01:00
|
|
|
const opts = this._getOAuthConfig();
|
|
|
|
const oldToken = this.runtimeData.get(this.name + 'OAuthToken');
|
2017-11-27 19:00:39 +01:00
|
|
|
if (this._tokenIsValid(oldToken)) {
|
2016-07-17 13:30:38 +02:00
|
|
|
this._oauthToken = oldToken;
|
2017-11-27 19:00:39 +01:00
|
|
|
return callback();
|
2016-03-27 14:57:22 +02:00
|
|
|
}
|
2019-08-16 23:05:39 +02:00
|
|
|
const url =
|
|
|
|
opts.url +
|
|
|
|
'?client_id={cid}&scope={scope}&response_type=token&redirect_uri={url}'
|
|
|
|
.replace('{cid}', encodeURIComponent(opts.clientId))
|
|
|
|
.replace('{scope}', encodeURIComponent(opts.scope))
|
|
|
|
.replace('{url}', encodeURIComponent(this._getOauthRedirectUrl()));
|
2017-06-11 19:41:56 +02:00
|
|
|
this.logger.debug('OAuth: popup opened');
|
2017-12-25 18:58:23 +01:00
|
|
|
const popupWindow = this._openPopup(url, 'OAuth', opts.width, opts.height);
|
|
|
|
if (!popupWindow) {
|
2017-11-27 19:00:39 +01:00
|
|
|
return callback('OAuth: cannot open popup');
|
2016-03-27 15:36:07 +02:00
|
|
|
}
|
2017-12-25 18:58:23 +01:00
|
|
|
this._popupOpened(popupWindow);
|
2017-01-31 07:50:28 +01:00
|
|
|
const popupClosed = () => {
|
2016-03-27 14:57:22 +02:00
|
|
|
Backbone.off('popup-closed', popupClosed);
|
|
|
|
window.removeEventListener('message', windowMessage);
|
2016-07-17 13:30:38 +02:00
|
|
|
this.logger.error('OAuth error', 'popup closed');
|
2017-06-11 19:41:56 +02:00
|
|
|
callback('OAuth: popup closed');
|
2016-03-27 14:57:22 +02:00
|
|
|
};
|
2017-01-31 07:50:28 +01:00
|
|
|
const windowMessage = e => {
|
2016-03-27 14:57:22 +02:00
|
|
|
if (!e.data) {
|
|
|
|
return;
|
|
|
|
}
|
2017-12-09 21:46:08 +01:00
|
|
|
const token = this._oauthProcessReturn(e.data);
|
|
|
|
if (token) {
|
|
|
|
Backbone.off('popup-closed', popupClosed);
|
|
|
|
window.removeEventListener('message', windowMessage);
|
|
|
|
if (token.error) {
|
|
|
|
this.logger.error('OAuth error', token.error, token.errorDescription);
|
|
|
|
callback('OAuth: ' + token.error);
|
|
|
|
} else {
|
|
|
|
callback();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this.logger.debug('Skipped OAuth message', e.data);
|
|
|
|
}
|
2016-03-27 14:57:22 +02:00
|
|
|
};
|
|
|
|
Backbone.on('popup-closed', popupClosed);
|
|
|
|
window.addEventListener('message', windowMessage);
|
|
|
|
},
|
|
|
|
|
2019-08-16 23:05:39 +02:00
|
|
|
_popupOpened(popupWindow) {},
|
2017-12-25 18:58:23 +01:00
|
|
|
|
2017-12-09 21:46:08 +01:00
|
|
|
_oauthProcessReturn: function(message) {
|
2017-04-16 18:05:58 +02:00
|
|
|
const token = this._oauthMsgToToken(message);
|
2017-12-09 21:46:08 +01:00
|
|
|
if (token && !token.error) {
|
2017-04-16 18:05:58 +02:00
|
|
|
this._oauthToken = token;
|
|
|
|
this.runtimeData.set(this.name + 'OAuthToken', token);
|
|
|
|
this.logger.debug('OAuth token received');
|
|
|
|
}
|
2017-12-09 21:46:08 +01:00
|
|
|
return token;
|
2017-04-16 18:05:58 +02:00
|
|
|
},
|
|
|
|
|
2016-03-27 14:57:22 +02:00
|
|
|
_oauthMsgToToken: function(data) {
|
2017-12-09 21:46:08 +01:00
|
|
|
if (!data.token_type) {
|
|
|
|
if (data.error) {
|
2019-08-16 23:05:39 +02:00
|
|
|
return { error: data.error, errorDescription: data.error_description };
|
2017-12-09 21:46:08 +01:00
|
|
|
} else {
|
|
|
|
return undefined;
|
2017-12-09 12:39:18 +01:00
|
|
|
}
|
2016-03-27 14:57:22 +02:00
|
|
|
}
|
|
|
|
return {
|
2017-11-27 19:00:39 +01:00
|
|
|
dt: Date.now() - 60 * 1000,
|
2016-03-27 14:57:22 +02:00
|
|
|
tokenType: data.token_type,
|
|
|
|
accessToken: data.access_token,
|
|
|
|
authenticationToken: data.authentication_token,
|
2017-11-27 19:00:39 +01:00
|
|
|
expiresIn: +data.expires_in,
|
2016-03-27 14:57:22 +02:00
|
|
|
scope: data.scope,
|
|
|
|
userId: data.user_id
|
|
|
|
};
|
2016-03-27 15:18:05 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
_oauthRefreshToken: function(callback) {
|
|
|
|
this._oauthToken.expired = true;
|
|
|
|
this.runtimeData.set(this.name + 'OAuthToken', this._oauthToken);
|
|
|
|
this._oauthAuthorize(callback);
|
2016-06-04 17:08:50 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
_oauthRevokeToken: function(url) {
|
2017-01-31 07:50:28 +01:00
|
|
|
const token = this.runtimeData.get(this.name + 'OAuthToken');
|
2016-06-04 17:08:50 +02:00
|
|
|
if (token) {
|
2017-04-16 17:00:35 +02:00
|
|
|
if (url) {
|
|
|
|
this._xhr({
|
|
|
|
url: url.replace('{token}', token.accessToken),
|
|
|
|
statuses: [200, 401]
|
|
|
|
});
|
|
|
|
}
|
2016-06-04 17:08:50 +02:00
|
|
|
this.runtimeData.unset(this.name + 'OAuthToken');
|
|
|
|
this._oauthToken = null;
|
|
|
|
}
|
2017-11-27 19:00:39 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
_tokenIsValid(token) {
|
|
|
|
if (!token || token.expired) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (token.dt && token.expiresIn && token.dt + token.expiresIn * 1000 < Date.now()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
2016-03-27 09:06:23 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
StorageBase.extend = Backbone.Model.extend;
|
|
|
|
|
|
|
|
module.exports = StorageBase;
|