Merge "Support names and ids for users, projects and domains in Keystone"

This commit is contained in:
Jenkins 2016-10-17 21:49:20 +00:00 committed by Gerrit Code Review
commit 8828ec6382
3 changed files with 224 additions and 51 deletions

View File

@ -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.<T>} 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))

View File

@ -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();

View File

@ -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());