@itentialopensource/adapter-openstack_keystone
Version:
This adapter integrates with system described as: Openstack Keystone.
290 lines (251 loc) • 7.74 kB
JavaScript
/* global log */
const util = require('util');
const https = require('https');
const axios = require('axios').default;
const inspect = (obj) => util.inspect(obj, { depth: Infinity });
let authLoggingAllowed = false;
const AUTH_URL = '/v3/auth/tokens';
const cache = {
token: '',
expires_at: '0'
};
// locally stored token is valid if its expiry time is longer then 1 minute
const isTokenvalid = () => (new Date(cache.expires_at).valueOf() - new Date().valueOf()) / 1000 / 60 > 1;
/**
* @name sendTokenrequest
* @summary Gets auth token from Keystone service
* If current token is still valid, returns it.
* @param {string} [url] - keystone url
* @param {object} [data] - request body to send in auth request
* @return {string} token to authenticate openstack service operation
*/
const sendTokenrequest = async (url, data) => {
if (isTokenvalid()) {
log.debug('Using cached token');
return cache.token;
}
// https://axios-http.com/docs/req_config
// https://nodejs.org/api/https.html#new-agentoptions
const config = {
method: 'post',
httpsAgent: new https.Agent({ rejectUnauthorized: false }),
url,
data
};
if (authLoggingAllowed) {
log.debug('sendTokenrequest config', inspect(config));
}
const response = await axios(config).catch((err) => {
if (authLoggingAllowed) {
log.error('err.message', err.message);
if (err.response) {
log.error('res.response.data', err.response.data);
}
}
throw new Error('Token could not be obtained');
});
if (!response) {
log.error('No token');
throw new Error('Token could not be obtained');
}
if (authLoggingAllowed) {
log.debug('response.headers', response.headers);
log.debug('response.data', inspect(response.data));
}
cache.token = response.headers['x-subject-token'];
if (response.data.token.expires_at) {
cache.expires_at = response.data.token.expires_at;
} else {
cache.expires_at = '0';
}
return cache.token;
};
const buildUserDomain = (authProps) => {
const userDomain = {};
if (authProps.os_user_domain_name) {
userDomain.name = authProps.os_user_domain_name;
} else {
userDomain.id = authProps.os_user_domain_id;
}
return userDomain;
};
const buildUser = (authProps) => {
const user = {
password: authProps.os_password
};
if (authProps.os_user_id) {
user.id = authProps.os_user_id;
} else {
user.name = authProps.os_username;
user.domain = buildUserDomain(authProps);
}
return user;
};
const buildProjectDomain = (authProps) => {
const domain = {};
if (authProps.os_project_domain_name) {
domain.name = authProps.os_project_domain_name;
}
if (authProps.os_project_domain_id) {
domain.id = authProps.os_project_domain_id;
}
return domain;
};
const buildProjectScopeWithProjectId = (authProps) => {
const scope = {
project: {
id: authProps.os_project_id
}
};
return scope;
};
const buildProjectScopeWithProjectName = (authProps) => {
const scope = {
project: {
domain: buildProjectDomain(authProps),
name: authProps.os_project_name
}
};
return scope;
};
const buildScopeWithDomainId = (authProps) => {
const scope = {
domain: {
id: authProps.os_domain_id
}
};
return scope;
};
const buildScopeWithDomainName = (authProps) => {
const scope = {
domain: {
name: authProps.os_domain_name
}
};
return scope;
};
/* eslint-disable-next-line no-unused-vars */
const buildSystemScope = () => {
const scope = {
system: {
all: true
}
};
return scope;
};
const buildScope = (authProps) => {
if (authProps.os_project_id) return buildProjectScopeWithProjectId(authProps);
if (authProps.os_project_name) return buildProjectScopeWithProjectName(authProps);
if (authProps.os_domain_id) return buildScopeWithDomainId(authProps);
if (authProps.os_domain_name) return buildScopeWithDomainName(authProps);
return buildSystemScope();
};
const tokenImpl = {};
/**
* @name passwordAuthenticationWithUnscopedAuthorization
* @summary Password authentication with unscoped authorization.
* Check: https://docs.openstack.org/api-ref/identity/v3/?expanded=password-authentication-with-scoped-authorization-detail,validate-and-show-information-for-token-detail#password-authentication-with-unscoped-authorization
*
* @param {object} [authURL] - keystone authentication URL
* @param {object} [authProps] - properties for building authentication request
* @return {string} token to authenticate openstack service operation
*/
tokenImpl.passwordAuthenticationWithUnscopedAuthorization = async (authURL, authProps) => {
const requestBody = {
auth: {
identity: {
methods: [
'password'
],
password: {
user: buildUser(authProps)
}
}
}
};
return sendTokenrequest(authURL, requestBody);
};
/**
* @name passwordAuthenticationWithScopedAuthorization
* @summary Password authentication with scoped authorization.
* Check: https://docs.openstack.org/api-ref/identity/v3/?expanded=#password-authentication-with-scoped-authorization
*
* @param {object} [authURL] - keystone authentication URL
* @param {object} [authProps] - properties for building authentication request
* @return {string} token to authenticate openstack service operation
*/
tokenImpl.passwordAuthenticationWithScopedAuthorization = async (authURL, authProps) => {
const requestBody = {
auth: {
identity: {
methods: [
'password'
],
password: {
user: buildUser(authProps)
}
},
scope: buildScope(authProps)
}
};
return sendTokenrequest(authURL, requestBody);
};
tokenImpl.passwordAuthenticationWithExplicitUnscopedAuthorization = () => {
throw new Error('not implemented');
};
tokenImpl.tokenAuthenticationWithUnscopedAuthorization = () => {
throw new Error('not implemented');
};
tokenImpl.tokenAuthenticationWithScopedAuthorization = () => {
throw new Error('not implemented');
};
tokenImpl.tokenAuthenticationWithExplicitUnscopedAuthorization = () => {
throw new Error('not implemented');
};
tokenImpl.multiStepAuthentication = () => {
throw new Error('not implemented');
};
tokenImpl.authenticatingWithAnApplicationCredential = () => {
throw new Error('not implemented');
};
const extractAuthProps = (properties) => {
const authProps = {};
Object.keys(properties.authentication).forEach((k) => {
if (k.startsWith('os_')) {
authProps[k] = properties.authentication[k];
}
});
if (authLoggingAllowed) {
log.debug('authProps', authProps);
}
return authProps;
};
const extractAuthUrl = (authProps) => {
let authURL = authProps.os_auth_url.concat(AUTH_URL);
if (authProps.os_auth_url.endsWith('/')) {
authURL = authProps.os_auth_url.slice(0, -1).concat(AUTH_URL);
}
return authURL;
};
/**
* @name getToken
* @summary Gets auth token from Keystone service to authorize openstack operation
*
* @param {object} [properties] - properties for building authentication request
* @param {string} [method] - openstack authentication type
* @return {string} token to authenticate openstack service operation
*/
const getToken = async (properties, method = 'passwordAuthenticationWithScopedAuthorization') => {
if (properties.stub) {
return 'fake_token';
}
authLoggingAllowed = properties.authentication.auth_logging;
const authProps = extractAuthProps(properties);
const authURL = extractAuthUrl(authProps);
const token = await tokenImpl[method](authURL, authProps);
if (authLoggingAllowed) {
log.debug('token', token);
}
return token;
};
module.exports = getToken;