Browse Source

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

In Keystone API it's possible to provide both ids and names for
users, projects, user domains and project domains. This commit
adds support for this functionality.

Change-Id: I3268bd82cc92a150927c98e0827ebd105d91f5e3
Vitaly Kramskikh 2 years ago
parent
commit
3956dbe65b
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