diff --git a/src/keystone.js b/src/keystone.js index 5f8a7af..8790097 100644 --- a/src/keystone.js +++ b/src/keystone.js @@ -70,62 +70,77 @@ export default class Keystone extends AbstractService { /** * Issue a token from the provided credentials. Credentials will be read from the - * configuration, unless they have been explicitly provided. Note that both the userDomainName - * and the projectDomainName are only required if the user/project names are given, rather - * than the explicit user/domain ID's. + * configuration, unless they have been explicitly provided. * * NOTE: This method is only applicable if the password auth plugin on keystone is enabled. * Other auth methods will have to be provided by third-party developers. * - * @param {String} username An optional user name or ID. - * @param {String} password An optional password. - * @param {String} projectName An optional project name or ID. - * @param {String} userDomainName Domain name for the user, not required if a user id is given. - * @param {String} projectDomainName Domain name for the project, not required with project ID. + * @param {Object} credentials Optional credentials. + * @param {String} credentials.user_id An optional user ID. + * @param {String} credentials.username An optional user name. + * @param {String} credentials.password An optional password. + * @param {String} credentials.user_domain_id An optional user domain ID. + * Not required if a user ID is given. + * @param {String} credentials.user_domain_name An optional user domain name. + * Not required if a user ID is given. + * @param {String} credentials.project_id An optional project ID. + * @param {String} credentials.project_name An optional project name. + * @param {String} credentials.project_domain_id An optional project domain ID. + * Not required if a project ID is given. + * @param {String} credentials.project_domain_name An optional project domain name. + * Not required if a project ID is given. * @returns {Promise.} A promise which will resolve with a valid token. */ - tokenIssue(username = this._safeConfigGet('auth.username'), - password = this._safeConfigGet('auth.password'), - projectName = this._safeConfigGet('auth.project_name'), - userDomainName = this._safeConfigGet('auth.user_domain_id'), - projectDomainName = this._safeConfigGet('auth.project_domain_id')) { + tokenIssue({ + user_id: userId = this._safeConfigGet('auth.user_id'), + username = this._safeConfigGet('auth.username'), + password = this._safeConfigGet('auth.password'), + user_domain_id: userDomainId = this._safeConfigGet('auth.user_domain_id'), + user_domain_name: userDomainName = this._safeConfigGet('auth.user_domain_name'), + project_id: projectId = this._safeConfigGet('auth.project_id'), + project_name: projectName = this._safeConfigGet('auth.project_name'), + project_domain_id: projectDomainId = this._safeConfigGet('auth.project_domain_id'), + project_domain_name: projectDomainName = this._safeConfigGet('auth.project_domain_name') + } = {}) { + let project; + let user = {password}; + + if (userId) { + user.id = userId; + } else if (username) { + user.name = username; + if (userDomainId) { + user.domain = {id: userDomainId}; + } else if (userDomainName) { + user.domain = {name: userDomainName}; + } else { + user.domain = {id: 'default'}; + } + } + + if (projectId) { + project = {id: projectId}; + } else if (projectName) { + project = {name: projectName}; + if (projectDomainId) { + project.domain = {id: projectDomainId}; + } else if (projectDomainName) { + project.domain = {name: projectDomainName}; + } else { + project.domain = {id: 'default'}; + } + } const body = { auth: { identity: { methods: ['password'], - password: { - user: { - name: username, - password: password - } - } - } + password: {user} + }, + scope: project ? {project} : 'unscoped' } }; - if (userDomainName) { - body.auth.identity.password.user.domain = { - id: userDomainName - }; - } - - if (!projectName) { - body.auth.scope = "unscoped"; - } else { - body.auth.scope = { - project: { - name: projectName - } - }; - - if (projectDomainName) { - body.auth.scope.project.domain = { - id: projectDomainName - }; - } - } - return this .serviceEndpoint() .then((url) => this.http.httpPost(`${url}auth/tokens`, body)) diff --git a/test/functional/keystoneTest.js b/test/functional/keystoneTest.js index aa6d77c..11a5e92 100644 --- a/test/functional/keystoneTest.js +++ b/test/functional/keystoneTest.js @@ -94,12 +94,7 @@ describe("Keystone", () => { it("should permit passing your own user, password, and project.", (done) => { keystone - .tokenIssue( - adminConfig.auth.username, - adminConfig.auth.password, - adminConfig.auth.project_name, - adminConfig.auth.user_domain_id, - adminConfig.auth.project_domain_id) + .tokenIssue(adminConfig.auth) .then((token) => { expect(token).not.toBeNull(); done(); @@ -109,9 +104,38 @@ describe("Keystone", () => { ); }); - it("should throw an exception if invalid credentials are provided.", (done) => { + it("should throw an exception if invalid username and password are provided.", (done) => { keystone - .tokenIssue('foo', 'bar', 'lolProject', 'notADomain', 'notADomain') + .tokenIssue({ + username: 'foo', + password: 'bar' + }) + .then((token) => done.fail(token)) + .catch((error) => { + expect(error).not.toBeNull(); + done(); + }); + }); + + it("should throw an exception if invalid project is provided.", (done) => { + keystone + .tokenIssue({ + project_id: 'foo', + project_name: 'bar' + }) + .then((token) => done.fail(token)) + .catch((error) => { + expect(error).not.toBeNull(); + done(); + }); + }); + + it("should throw an exception if invalid user domain is provided.", (done) => { + keystone + .tokenIssue({ + user_domain_id: 'foo', + user_domain_name: 'bar' + }) .then((token) => done.fail(token)) .catch((error) => { expect(error).not.toBeNull(); diff --git a/test/unit/keystoneTest.js b/test/unit/keystoneTest.js index d10fb65..e5cc610 100644 --- a/test/unit/keystoneTest.js +++ b/test/unit/keystoneTest.js @@ -70,8 +70,10 @@ describe('Keystone', () => { describe("tokenIssue()", () => { it("should 'just work' by using provided credentials from the config.", (done) => { + let mockOptions = mockData.tokenIssue(); fetchMock.mock(mockData.root()); - fetchMock.mock(mockData.tokenIssue()); + fetchMock.mock(mockOptions); + const keystone = new Keystone(mockData.config); keystone .tokenIssue() @@ -82,6 +84,138 @@ describe('Keystone', () => { .catch((error) => done.fail(error)); }); + it("should support authentication with a user ID", (done) => { + let mockOptions = mockData.tokenIssue(); + fetchMock.mock(mockData.root()); + fetchMock.mock(mockOptions); + + const userId = 'userId'; + + const keystone = new Keystone(mockData.config); + keystone + .tokenIssue({ + user_id: userId + }) + .then(() => { + const requestBody = JSON.parse(fetchMock.lastCall(mockOptions.matcher)[1].body); + expect(requestBody.auth.identity.password.user.id).toEqual(userId); + done(); + }) + .catch((error) => done.fail(error)); + }); + + it("should support authentication with a username and a user domain ID", (done) => { + let mockOptions = mockData.tokenIssue(); + fetchMock.mock(mockData.root()); + fetchMock.mock(mockOptions); + + const username = 'username'; + const userDomainId = 'userDomainId'; + + const keystone = new Keystone(mockData.config); + keystone + .tokenIssue({ + username: username, + user_domain_id: userDomainId + }) + .then(() => { + const requestBody = JSON.parse(fetchMock.lastCall(mockOptions.matcher)[1].body); + expect(requestBody.auth.identity.password.user.name).toEqual(username); + expect(requestBody.auth.identity.password.user.domain.id).toEqual(userDomainId); + done(); + }) + .catch((error) => done.fail(error)); + }); + + it("should support authentication with a username and a user domain name", (done) => { + let mockOptions = mockData.tokenIssue(); + fetchMock.mock(mockData.root()); + fetchMock.mock(mockOptions); + + const username = 'username'; + const userDomainName = 'userDomainName'; + + const keystone = new Keystone(mockData.config); + keystone + .tokenIssue({ + username: username, + user_domain_name: userDomainName + }) + .then(() => { + const requestBody = JSON.parse(fetchMock.lastCall(mockOptions.matcher)[1].body); + expect(requestBody.auth.identity.password.user.name).toEqual(username); + expect(requestBody.auth.identity.password.user.domain.name).toEqual(userDomainName); + done(); + }) + .catch((error) => done.fail(error)); + }); + + it("should support authentication with a project ID", (done) => { + let mockOptions = mockData.tokenIssue(); + fetchMock.mock(mockData.root()); + fetchMock.mock(mockOptions); + + const projectId = 'projectId'; + + const keystone = new Keystone(mockData.config); + keystone + .tokenIssue({ + project_id: projectId, + }) + .then(() => { + const requestBody = JSON.parse(fetchMock.lastCall(mockOptions.matcher)[1].body); + expect(requestBody.auth.scope.project.id).toEqual(projectId); + done(); + }) + .catch((error) => done.fail(error)); + }); + + it("should support authentication with a project name and a project domain ID", (done) => { + let mockOptions = mockData.tokenIssue(); + fetchMock.mock(mockData.root()); + fetchMock.mock(mockOptions); + + const projectName = 'projectName'; + const projectDomainId = 'projectDomainId'; + + const keystone = new Keystone(mockData.config); + keystone + .tokenIssue({ + project_name: projectName, + project_domain_id: projectDomainId + }) + .then(() => { + const requestBody = JSON.parse(fetchMock.lastCall(mockOptions.matcher)[1].body); + expect(requestBody.auth.scope.project.name).toEqual(projectName); + expect(requestBody.auth.scope.project.domain.id).toEqual(projectDomainId); + done(); + }) + .catch((error) => done.fail(error)); + }); + + it("should support authentication with a project name and a project domain name", (done) => { + let mockOptions = mockData.tokenIssue(); + fetchMock.mock(mockData.root()); + fetchMock.mock(mockOptions); + + const projectName = 'projectName'; + const projectDomainName = 'projectDomainName'; + + const keystone = new Keystone(mockData.config); + keystone + .tokenIssue({ + project_name: projectName, + project_domain_name: projectDomainName + }) + .then(() => { + const requestBody = JSON.parse(fetchMock.lastCall(mockOptions.matcher)[1].body); + expect(requestBody.auth.scope.project.name).toEqual(projectName); + expect(requestBody.auth.scope.project.domain.name).toEqual(projectDomainName); + done(); + }) + .catch((error) => done.fail(error)); + }); + it("Should not cache its results", (done) => { let mockOptions = mockData.tokenIssue(); fetchMock.mock(mockData.root());