UNPKG

@itentialopensource/adapter-openstack_keystone

Version:

This adapter integrates with system described as: Openstack Keystone.

290 lines (251 loc) 7.74 kB
/* 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;