UserSource.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. /*
  2. Copyright (C) 2017 Cloudbase Solutions SRL
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU Affero General Public License as
  5. published by the Free Software Foundation, either version 3 of the
  6. License, or (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU Affero General Public License for more details.
  11. You should have received a copy of the GNU Affero General Public License
  12. along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. */
  14. import cookie from "js-cookie";
  15. import Api from "@src/utils/ApiCaller";
  16. import configLoader from "@src/utils/Config";
  17. import type { Credentials, User } from "@src/@types/User";
  18. import type { Role, Project, RoleAssignment } from "@src/@types/Project";
  19. import utils from "@src/utils/ObjectUtils";
  20. class UserModel {
  21. static parseUserData(data: any) {
  22. const newData = {
  23. id: data.token.user.id,
  24. name: data.token.user.name,
  25. email: data.token.user.email,
  26. project: data.token.project,
  27. };
  28. return newData;
  29. }
  30. }
  31. class UserSource {
  32. saveDomainName(domainName: string) {
  33. localStorage.setItem("userDomainName", domainName);
  34. }
  35. get domainName(): string {
  36. return configLoader.config.showUserDomainInput
  37. ? localStorage.getItem("userDomainName") ||
  38. configLoader.config.defaultUserDomain
  39. : configLoader.config.defaultUserDomain;
  40. }
  41. async login(userData: Credentials): Promise<any> {
  42. const auth = {
  43. auth: {
  44. identity: {
  45. methods: ["password"],
  46. password: {
  47. user: {
  48. name: userData.name,
  49. domain: { name: userData.domain },
  50. password: userData.password,
  51. },
  52. },
  53. },
  54. scope: "unscoped",
  55. },
  56. };
  57. Api.setDefaultHeader("X-Auth-Token", null);
  58. const response = await Api.send({
  59. url: `${configLoader.config.servicesUrls.keystone}/auth/tokens`,
  60. method: "POST",
  61. data: auth,
  62. });
  63. const token = response.headers
  64. ? response.headers["X-Subject-Token"] ||
  65. response.headers["x-subject-token"]
  66. : "";
  67. Api.setDefaultHeader("X-Auth-Token", token);
  68. cookie.set("unscopedToken", token, { expires: 30 });
  69. return response.data;
  70. }
  71. async loginScoped(projectId: string, skipCookie?: boolean): Promise<User> {
  72. const useProjectId = skipCookie
  73. ? projectId
  74. : cookie.get("projectId") || projectId;
  75. const token = cookie.get("unscopedToken");
  76. const auth = {
  77. auth: {
  78. identity: {
  79. methods: ["token"],
  80. token: {
  81. id: token,
  82. },
  83. },
  84. scope: {
  85. project: {
  86. id: useProjectId,
  87. },
  88. },
  89. },
  90. };
  91. Api.setDefaultHeader("X-Auth-Token", null);
  92. try {
  93. const response = await Api.send({
  94. url: `${configLoader.config.servicesUrls.keystone}/auth/tokens`,
  95. method: "POST",
  96. data: auth,
  97. });
  98. const subjectToken = response.headers
  99. ? response.headers["X-Subject-Token"] ||
  100. response.headers["x-subject-token"]
  101. : "";
  102. let data: User = UserModel.parseUserData(response.data);
  103. data = { ...data, token: subjectToken };
  104. cookie.set("token", data.token || "", { expires: 30 });
  105. cookie.set("projectId", data.project.id, { expires: 30 });
  106. Api.setDefaultHeader("X-Auth-Token", data.token || "");
  107. return data;
  108. } catch (err) {
  109. if (!skipCookie) {
  110. const user: User = await this.loginScoped(projectId, true);
  111. return user;
  112. }
  113. throw err;
  114. }
  115. }
  116. async tokenLogin(): Promise<User> {
  117. const token = cookie.get("token") || "";
  118. if (token) {
  119. Api.setDefaultHeader("X-Auth-Token", token);
  120. }
  121. try {
  122. const response = await Api.send({
  123. url: `${configLoader.config.servicesUrls.keystone}/auth/tokens`,
  124. headers: { "X-Subject-Token": token },
  125. });
  126. let data: User = UserModel.parseUserData(response.data);
  127. data = { ...data, token };
  128. return data;
  129. } catch (err) {
  130. cookie.remove("token");
  131. Api.setDefaultHeader("X-Auth-Token", null);
  132. throw err;
  133. }
  134. }
  135. async switchProject(): Promise<void> {
  136. const token = cookie.get("unscopedToken");
  137. if (token) {
  138. cookie.remove("projectId");
  139. return;
  140. }
  141. throw new Error("No unscoped token");
  142. }
  143. async logout(): Promise<void> {
  144. const token = cookie.get("token");
  145. const clear = () => {
  146. cookie.remove("token");
  147. window.location.href = "/login";
  148. Api.setDefaultHeader("X-Auth-Token", null);
  149. };
  150. try {
  151. await Api.send({
  152. url: `${configLoader.config.servicesUrls.keystone}/auth/tokens`,
  153. method: "DELETE",
  154. headers: { "X-Subject-Token": token || "" },
  155. });
  156. } finally {
  157. clear();
  158. }
  159. }
  160. async getUserInfo(userId: string): Promise<User> {
  161. const response = await Api.get(
  162. `${configLoader.config.servicesUrls.keystone}/users/${userId}`
  163. );
  164. return response.data.user;
  165. }
  166. async getAllUsers(skipLog?: boolean, quietError?: boolean): Promise<User[]> {
  167. const response = await Api.send({
  168. url: `${configLoader.config.servicesUrls.keystone}/users`,
  169. skipLog,
  170. quietError,
  171. });
  172. let users: User[] = response.data.users;
  173. await utils.waitFor(() => Boolean(configLoader.config));
  174. users = users
  175. .filter(u => !configLoader.config.hiddenUsers.find(hu => hu === u.name))
  176. .sort((u1, u2) => u1.name.localeCompare(u2.name));
  177. return users;
  178. }
  179. async update(
  180. userId: string,
  181. user: Partial<User>,
  182. oldUser: User | null
  183. ): Promise<User> {
  184. const data: any = { user: {} };
  185. const oldData: any = oldUser || {};
  186. if (user.email || oldData.email) {
  187. data.user.email = user.email;
  188. }
  189. if (user.description || oldData.description) {
  190. data.user.description = user.description;
  191. }
  192. if (user.enabled != null) {
  193. data.user.enabled = user.enabled;
  194. }
  195. if (user.name) {
  196. data.user.name = user.name;
  197. }
  198. if (user.password) {
  199. data.user.password = user.password;
  200. }
  201. if (user.project_id || oldData.project_id) {
  202. data.user.project_id = user.project_id;
  203. }
  204. const response = await Api.send({
  205. url: `${configLoader.config.servicesUrls.keystone}/users/${userId}`,
  206. method: "PATCH",
  207. data,
  208. });
  209. let updatedUser: User = response.data.user;
  210. if (updatedUser.extra) {
  211. updatedUser = {
  212. ...updatedUser,
  213. ...updatedUser.extra,
  214. };
  215. }
  216. // if project id was updated, assign him to that project, if his not already assigned
  217. if (data.user.project_id) {
  218. const projects: Project[] = await this.getProjects(updatedUser.id);
  219. if (projects.find(p => p.id === data.user.project_id)) {
  220. return updatedUser;
  221. }
  222. await this.assignUserToProject(
  223. updatedUser.id,
  224. updatedUser.project_id || "undefined"
  225. );
  226. return updatedUser;
  227. }
  228. return updatedUser;
  229. }
  230. async add(user: User): Promise<User> {
  231. const data: any = { user: {} };
  232. data.user.name = user.name;
  233. data.user.password = user.password || "";
  234. data.user.enabled = user.enabled == null ? true : user.enabled;
  235. if (user.email) {
  236. data.user.email = user.email;
  237. }
  238. if (user.description) {
  239. data.user.description = user.description;
  240. }
  241. if (user.project_id) {
  242. data.user.project_id = user.project_id;
  243. }
  244. const response = await Api.send({
  245. url: `${configLoader.config.servicesUrls.keystone}/users`,
  246. method: "POST",
  247. data,
  248. });
  249. let addedUser: User = response.data.user;
  250. if (addedUser.extra) {
  251. addedUser = {
  252. ...addedUser,
  253. ...addedUser.extra,
  254. };
  255. }
  256. // If the user has a project id set, assign him to that project with admin role
  257. if (addedUser.project_id) {
  258. await this.assignUserToProject(
  259. addedUser.id,
  260. addedUser.project_id || "undefined"
  261. );
  262. return addedUser;
  263. }
  264. return addedUser;
  265. }
  266. async delete(userId: string): Promise<void> {
  267. await Api.send({
  268. url: `${configLoader.config.servicesUrls.keystone}/users/${userId}`,
  269. method: "DELETE",
  270. });
  271. }
  272. async assignUserToProject(userId: string, projectId: string): Promise<void> {
  273. const roleId: string = await this.getMemberRoleId();
  274. await this.assignUserToProjectWithRole(userId, projectId, roleId);
  275. }
  276. async assignUserToProjectWithRole(
  277. userId: string,
  278. projectId: string,
  279. roleId: string
  280. ): Promise<void> {
  281. await Api.send({
  282. url: `${configLoader.config.servicesUrls.keystone}/projects/${projectId}/users/${userId}/roles/${roleId}`,
  283. method: "PUT",
  284. });
  285. }
  286. async getMemberRoleId(): Promise<string> {
  287. const roles: { id: string; name: string }[] = await this.getRoles();
  288. const role = roles.find(r => r.name === "_member_");
  289. const roleId = role ? role.id : "";
  290. return roleId;
  291. }
  292. async getAdminRoleId(): Promise<string> {
  293. const roles: { id: string; name: string }[] = await this.getRoles();
  294. const role = roles.find(
  295. r => r.name.toLowerCase() === configLoader.config.adminRoleName
  296. );
  297. const roleId = role ? role.id : "";
  298. return roleId;
  299. }
  300. async getRoles(): Promise<Role[]> {
  301. const response = await Api.get(
  302. `${configLoader.config.servicesUrls.keystone}/roles`
  303. );
  304. const roles: Role[] = response.data.roles;
  305. roles.sort((r1, r2) => r1.name.localeCompare(r2.name));
  306. return roles;
  307. }
  308. async getProjects(userId: string): Promise<Project[]> {
  309. const response = await Api.get(
  310. `${configLoader.config.servicesUrls.keystone}/role_assignments?include_names`
  311. );
  312. const assignments: RoleAssignment[] = response.data.role_assignments;
  313. const projects: Project[] = assignments
  314. .filter(a => a.user.id === userId)
  315. .filter(
  316. (a, i, arr) =>
  317. arr.findIndex(
  318. e =>
  319. e.scope.project &&
  320. a.scope.project &&
  321. e.scope.project.id === a.scope.project.id
  322. ) === i
  323. )
  324. .map(a => a.scope.project)
  325. .filter(utils.notEmpty);
  326. return projects;
  327. }
  328. async isAdmin(userId: string): Promise<boolean> {
  329. const response = await Api.send({
  330. url: `${configLoader.config.servicesUrls.keystone}/role_assignments?include_names`,
  331. quietError: true,
  332. });
  333. const roleAssignments: RoleAssignment[] = response.data.role_assignments;
  334. return (
  335. roleAssignments
  336. .filter(a => a && a.user && a.user.id === userId)
  337. .filter(
  338. a =>
  339. a &&
  340. a.role &&
  341. a.role.name &&
  342. a.role.name.toLowerCase() === configLoader.config.adminRoleName
  343. ).length > 0
  344. );
  345. }
  346. }
  347. export default new UserSource();