Browse Source

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

Jenkins 2 years ago
parent
commit
8828ec6382
3 changed files with 224 additions and 51 deletions
  1. 57
    42
      src/keystone.js
  2. 32
    8
      test/functional/keystoneTest.js
  3. 135
    1
      test/unit/keystoneTest.js

+ 57
- 42
src/keystone.js View File

@@ -70,62 +70,77 @@ export default class Keystone extends AbstractService {
70 70
 
71 71
   /**
72 72
    * Issue a token from the provided credentials. Credentials will be read from the
73
-   * configuration, unless they have been explicitly provided. Note that both the userDomainName
74
-   * and the projectDomainName are only required if the user/project names are given, rather
75
-   * than the explicit user/domain ID's.
73
+   * configuration, unless they have been explicitly provided.
76 74
    *
77 75
    * NOTE: This method is only applicable if the password auth plugin on keystone is enabled.
78 76
    * Other auth methods will have to be provided by third-party developers.
79 77
    *
80
-   * @param {String} username An optional user name or ID.
81
-   * @param {String} password An optional password.
82
-   * @param {String} projectName An optional project name or ID.
83
-   * @param {String} userDomainName Domain name for the user, not required if a user id is given.
84
-   * @param {String} projectDomainName Domain name for the project, not required with project ID.
78
+   * @param {Object} credentials Optional credentials.
79
+   * @param {String} credentials.user_id An optional user ID.
80
+   * @param {String} credentials.username An optional user name.
81
+   * @param {String} credentials.password An optional password.
82
+   * @param {String} credentials.user_domain_id An optional user domain ID.
83
+   *   Not required if a user ID is given.
84
+   * @param {String} credentials.user_domain_name An optional user domain name.
85
+   *   Not required if a user ID is given.
86
+   * @param {String} credentials.project_id An optional project ID.
87
+   * @param {String} credentials.project_name An optional project name.
88
+   * @param {String} credentials.project_domain_id An optional project domain ID.
89
+   *   Not required if a project ID is given.
90
+   * @param {String} credentials.project_domain_name An optional project domain name.
91
+   *   Not required if a project ID is given.
85 92
    * @returns {Promise.<T>} A promise which will resolve with a valid token.
86 93
    */
87
-  tokenIssue(username = this._safeConfigGet('auth.username'),
88
-              password = this._safeConfigGet('auth.password'),
89
-              projectName = this._safeConfigGet('auth.project_name'),
90
-              userDomainName = this._safeConfigGet('auth.user_domain_id'),
91
-              projectDomainName = this._safeConfigGet('auth.project_domain_id')) {
94
+  tokenIssue({
95
+    user_id: userId = this._safeConfigGet('auth.user_id'),
96
+    username = this._safeConfigGet('auth.username'),
97
+    password = this._safeConfigGet('auth.password'),
98
+    user_domain_id: userDomainId = this._safeConfigGet('auth.user_domain_id'),
99
+    user_domain_name: userDomainName = this._safeConfigGet('auth.user_domain_name'),
100
+    project_id: projectId = this._safeConfigGet('auth.project_id'),
101
+    project_name: projectName = this._safeConfigGet('auth.project_name'),
102
+    project_domain_id: projectDomainId = this._safeConfigGet('auth.project_domain_id'),
103
+    project_domain_name: projectDomainName = this._safeConfigGet('auth.project_domain_name')
104
+  } = {}) {
105
+    let project;
106
+    let user = {password};
107
+
108
+    if (userId) {
109
+      user.id = userId;
110
+    } else if (username) {
111
+      user.name = username;
112
+      if (userDomainId) {
113
+        user.domain = {id: userDomainId};
114
+      } else if (userDomainName) {
115
+        user.domain = {name: userDomainName};
116
+      } else {
117
+        user.domain = {id: 'default'};
118
+      }
119
+    }
120
+
121
+    if (projectId) {
122
+      project = {id: projectId};
123
+    } else if (projectName) {
124
+      project = {name: projectName};
125
+      if (projectDomainId) {
126
+        project.domain = {id: projectDomainId};
127
+      } else if (projectDomainName) {
128
+        project.domain = {name: projectDomainName};
129
+      } else {
130
+        project.domain = {id: 'default'};
131
+      }
132
+    }
92 133
 
93 134
     const body = {
94 135
       auth: {
95 136
         identity: {
96 137
           methods: ['password'],
97
-          password: {
98
-            user: {
99
-              name: username,
100
-              password: password
101
-            }
102
-          }
103
-        }
138
+          password: {user}
139
+        },
140
+        scope: project ? {project} : 'unscoped'
104 141
       }
105 142
     };
106 143
 
107
-    if (userDomainName) {
108
-      body.auth.identity.password.user.domain = {
109
-        id: userDomainName
110
-      };
111
-    }
112
-
113
-    if (!projectName) {
114
-      body.auth.scope = "unscoped";
115
-    } else {
116
-      body.auth.scope = {
117
-        project: {
118
-          name: projectName
119
-        }
120
-      };
121
-
122
-      if (projectDomainName) {
123
-        body.auth.scope.project.domain = {
124
-          id: projectDomainName
125
-        };
126
-      }
127
-    }
128
-
129 144
     return this
130 145
       .serviceEndpoint()
131 146
       .then((url) => this.http.httpPost(`${url}auth/tokens`, body))

+ 32
- 8
test/functional/keystoneTest.js View File

@@ -94,12 +94,7 @@ describe("Keystone", () => {
94 94
 
95 95
     it("should permit passing your own user, password, and project.", (done) => {
96 96
       keystone
97
-        .tokenIssue(
98
-          adminConfig.auth.username,
99
-          adminConfig.auth.password,
100
-          adminConfig.auth.project_name,
101
-          adminConfig.auth.user_domain_id,
102
-          adminConfig.auth.project_domain_id)
97
+        .tokenIssue(adminConfig.auth)
103 98
         .then((token) => {
104 99
           expect(token).not.toBeNull();
105 100
           done();
@@ -109,9 +104,38 @@ describe("Keystone", () => {
109 104
         );
110 105
     });
111 106
 
112
-    it("should throw an exception if invalid credentials are provided.", (done) => {
107
+    it("should throw an exception if invalid username and password are provided.", (done) => {
113 108
       keystone
114
-        .tokenIssue('foo', 'bar', 'lolProject', 'notADomain', 'notADomain')
109
+        .tokenIssue({
110
+          username: 'foo',
111
+          password: 'bar'
112
+        })
113
+        .then((token) => done.fail(token))
114
+        .catch((error) => {
115
+          expect(error).not.toBeNull();
116
+          done();
117
+        });
118
+    });
119
+
120
+    it("should throw an exception if invalid project is provided.", (done) => {
121
+      keystone
122
+        .tokenIssue({
123
+          project_id: 'foo',
124
+          project_name: 'bar'
125
+        })
126
+        .then((token) => done.fail(token))
127
+        .catch((error) => {
128
+          expect(error).not.toBeNull();
129
+          done();
130
+        });
131
+    });
132
+
133
+    it("should throw an exception if invalid user domain is provided.", (done) => {
134
+      keystone
135
+        .tokenIssue({
136
+          user_domain_id: 'foo',
137
+          user_domain_name: 'bar'
138
+        })
115 139
         .then((token) => done.fail(token))
116 140
         .catch((error) => {
117 141
           expect(error).not.toBeNull();

+ 135
- 1
test/unit/keystoneTest.js View File

@@ -70,8 +70,10 @@ describe('Keystone', () => {
70 70
   describe("tokenIssue()", () => {
71 71
 
72 72
     it("should 'just work' by using provided credentials from the config.", (done) => {
73
+      let mockOptions = mockData.tokenIssue();
73 74
       fetchMock.mock(mockData.root());
74
-      fetchMock.mock(mockData.tokenIssue());
75
+      fetchMock.mock(mockOptions);
76
+
75 77
       const keystone = new Keystone(mockData.config);
76 78
       keystone
77 79
         .tokenIssue()
@@ -82,6 +84,138 @@ describe('Keystone', () => {
82 84
         .catch((error) => done.fail(error));
83 85
     });
84 86
 
87
+    it("should support authentication with a user ID", (done) => {
88
+      let mockOptions = mockData.tokenIssue();
89
+      fetchMock.mock(mockData.root());
90
+      fetchMock.mock(mockOptions);
91
+
92
+      const userId = 'userId';
93
+
94
+      const keystone = new Keystone(mockData.config);
95
+      keystone
96
+        .tokenIssue({
97
+          user_id: userId
98
+        })
99
+        .then(() => {
100
+          const requestBody = JSON.parse(fetchMock.lastCall(mockOptions.matcher)[1].body);
101
+          expect(requestBody.auth.identity.password.user.id).toEqual(userId);
102
+          done();
103
+        })
104
+        .catch((error) => done.fail(error));
105
+    });
106
+
107
+    it("should support authentication with a username and a user domain ID", (done) => {
108
+      let mockOptions = mockData.tokenIssue();
109
+      fetchMock.mock(mockData.root());
110
+      fetchMock.mock(mockOptions);
111
+
112
+      const username = 'username';
113
+      const userDomainId = 'userDomainId';
114
+
115
+      const keystone = new Keystone(mockData.config);
116
+      keystone
117
+        .tokenIssue({
118
+          username: username,
119
+          user_domain_id: userDomainId
120
+        })
121
+        .then(() => {
122
+          const requestBody = JSON.parse(fetchMock.lastCall(mockOptions.matcher)[1].body);
123
+          expect(requestBody.auth.identity.password.user.name).toEqual(username);
124
+          expect(requestBody.auth.identity.password.user.domain.id).toEqual(userDomainId);
125
+          done();
126
+        })
127
+        .catch((error) => done.fail(error));
128
+    });
129
+
130
+    it("should support authentication with a username and a user domain name", (done) => {
131
+      let mockOptions = mockData.tokenIssue();
132
+      fetchMock.mock(mockData.root());
133
+      fetchMock.mock(mockOptions);
134
+
135
+      const username = 'username';
136
+      const userDomainName = 'userDomainName';
137
+
138
+      const keystone = new Keystone(mockData.config);
139
+      keystone
140
+        .tokenIssue({
141
+          username: username,
142
+          user_domain_name: userDomainName
143
+        })
144
+        .then(() => {
145
+          const requestBody = JSON.parse(fetchMock.lastCall(mockOptions.matcher)[1].body);
146
+          expect(requestBody.auth.identity.password.user.name).toEqual(username);
147
+          expect(requestBody.auth.identity.password.user.domain.name).toEqual(userDomainName);
148
+          done();
149
+        })
150
+        .catch((error) => done.fail(error));
151
+    });
152
+
153
+    it("should support authentication with a project ID", (done) => {
154
+      let mockOptions = mockData.tokenIssue();
155
+      fetchMock.mock(mockData.root());
156
+      fetchMock.mock(mockOptions);
157
+
158
+      const projectId = 'projectId';
159
+
160
+      const keystone = new Keystone(mockData.config);
161
+      keystone
162
+        .tokenIssue({
163
+          project_id: projectId,
164
+        })
165
+        .then(() => {
166
+          const requestBody = JSON.parse(fetchMock.lastCall(mockOptions.matcher)[1].body);
167
+          expect(requestBody.auth.scope.project.id).toEqual(projectId);
168
+          done();
169
+        })
170
+        .catch((error) => done.fail(error));
171
+    });
172
+
173
+    it("should support authentication with a project name and a project domain ID", (done) => {
174
+      let mockOptions = mockData.tokenIssue();
175
+      fetchMock.mock(mockData.root());
176
+      fetchMock.mock(mockOptions);
177
+
178
+      const projectName = 'projectName';
179
+      const projectDomainId = 'projectDomainId';
180
+
181
+      const keystone = new Keystone(mockData.config);
182
+      keystone
183
+        .tokenIssue({
184
+          project_name: projectName,
185
+          project_domain_id: projectDomainId
186
+        })
187
+        .then(() => {
188
+          const requestBody = JSON.parse(fetchMock.lastCall(mockOptions.matcher)[1].body);
189
+          expect(requestBody.auth.scope.project.name).toEqual(projectName);
190
+          expect(requestBody.auth.scope.project.domain.id).toEqual(projectDomainId);
191
+          done();
192
+        })
193
+        .catch((error) => done.fail(error));
194
+    });
195
+
196
+    it("should support authentication with a project name and a project domain name", (done) => {
197
+      let mockOptions = mockData.tokenIssue();
198
+      fetchMock.mock(mockData.root());
199
+      fetchMock.mock(mockOptions);
200
+
201
+      const projectName = 'projectName';
202
+      const projectDomainName = 'projectDomainName';
203
+
204
+      const keystone = new Keystone(mockData.config);
205
+      keystone
206
+        .tokenIssue({
207
+          project_name: projectName,
208
+          project_domain_name: projectDomainName
209
+        })
210
+        .then(() => {
211
+          const requestBody = JSON.parse(fetchMock.lastCall(mockOptions.matcher)[1].body);
212
+          expect(requestBody.auth.scope.project.name).toEqual(projectName);
213
+          expect(requestBody.auth.scope.project.domain.name).toEqual(projectDomainName);
214
+          done();
215
+        })
216
+        .catch((error) => done.fail(error));
217
+    });
218
+
85 219
     it("Should not cache its results", (done) => {
86 220
       let mockOptions = mockData.tokenIssue();
87 221
       fetchMock.mock(mockData.root());

Loading…
Cancel
Save