UserSource.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  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 '../utils/ApiCaller'
  16. import configLoader from '../utils/Config'
  17. import type { Credentials, User } from '../@types/User'
  18. import type { Role, Project, RoleAssignment } from '../@types/Project'
  19. import utils from '../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. getDomainName(): string {
  36. return localStorage.getItem('userDomainName') || configLoader.config.defaultUserDomain
  37. }
  38. async login(userData: Credentials): Promise<any> {
  39. const auth = {
  40. auth: {
  41. identity: {
  42. methods: ['password'],
  43. password: {
  44. user: {
  45. name: userData.name,
  46. domain: { name: userData.domain },
  47. password: userData.password,
  48. },
  49. },
  50. },
  51. scope: 'unscoped',
  52. },
  53. }
  54. Api.setDefaultHeader('X-Auth-Token', null)
  55. const response = await Api.send({
  56. url: `${configLoader.config.servicesUrls.keystone}/auth/tokens`,
  57. method: 'POST',
  58. data: auth,
  59. })
  60. const token = response.headers ? response.headers['X-Subject-Token'] || response.headers['x-subject-token'] : ''
  61. Api.setDefaultHeader('X-Auth-Token', token)
  62. cookie.set('unscopedToken', token, { expires: 30 })
  63. return response.data
  64. }
  65. async loginScoped(projectId: string, skipCookie?: boolean): Promise<User> {
  66. const useProjectId = skipCookie ? projectId : cookie.get('projectId') || projectId
  67. const token = cookie.get('unscopedToken')
  68. const auth = {
  69. auth: {
  70. identity: {
  71. methods: ['token'],
  72. token: {
  73. id: token,
  74. },
  75. },
  76. scope: {
  77. project: {
  78. id: useProjectId,
  79. },
  80. },
  81. },
  82. }
  83. Api.setDefaultHeader('X-Auth-Token', null)
  84. try {
  85. const response = await Api.send({
  86. url: `${configLoader.config.servicesUrls.keystone}/auth/tokens`,
  87. method: 'POST',
  88. data: auth,
  89. })
  90. const subjectToken = response.headers ? response.headers['X-Subject-Token'] || response.headers['x-subject-token'] : ''
  91. let data: User = UserModel.parseUserData(response.data)
  92. data = { ...data, token: subjectToken }
  93. cookie.set('token', data.token || '', { expires: 30 })
  94. cookie.set('projectId', data.project.id, { expires: 30 })
  95. Api.setDefaultHeader('X-Auth-Token', data.token || '')
  96. return data
  97. } catch (err) {
  98. if (!skipCookie) {
  99. const user: User = await this.loginScoped(projectId, true)
  100. return user
  101. }
  102. throw err
  103. }
  104. }
  105. async tokenLogin(): Promise<User> {
  106. const token = cookie.get('token') || ''
  107. if (token) {
  108. Api.setDefaultHeader('X-Auth-Token', token)
  109. }
  110. try {
  111. const response = await Api.send({
  112. url: `${configLoader.config.servicesUrls.keystone}/auth/tokens`,
  113. headers: { 'X-Subject-Token': token },
  114. })
  115. let data: User = UserModel.parseUserData(response.data)
  116. data = { ...data, token }
  117. return data
  118. } catch (err) {
  119. cookie.remove('token')
  120. Api.setDefaultHeader('X-Auth-Token', null)
  121. throw err
  122. }
  123. }
  124. async switchProject(): Promise<void> {
  125. const token = cookie.get('unscopedToken')
  126. if (token) {
  127. cookie.remove('projectId')
  128. return
  129. }
  130. throw new Error()
  131. }
  132. async logout(): Promise<void> {
  133. const token = cookie.get('token')
  134. const clear = () => {
  135. cookie.remove('token')
  136. window.location.href = '/login'
  137. Api.setDefaultHeader('X-Auth-Token', null)
  138. }
  139. try {
  140. await Api.send({
  141. url: `${configLoader.config.servicesUrls.keystone}/auth/tokens`,
  142. method: 'DELETE',
  143. headers: { 'X-Subject-Token': token || '' },
  144. })
  145. } finally {
  146. clear()
  147. }
  148. }
  149. async getUserInfo(userId: string): Promise<User> {
  150. const response = await Api.get(`${configLoader.config.servicesUrls.keystone}/users/${userId}`)
  151. return response.data.user
  152. }
  153. async getAllUsers(skipLog?: boolean): Promise<User[]> {
  154. const response = await Api.send({ url: `${configLoader.config.servicesUrls.keystone}/users`, skipLog })
  155. let users: User[] = response.data.users
  156. await utils.waitFor(() => Boolean(configLoader.config))
  157. users = users.filter(u => !configLoader.config.hiddenUsers.find(hu => hu === u.name))
  158. .sort((u1, u2) => u1.name.localeCompare(u2.name))
  159. return users
  160. }
  161. async update(userId: string, user: Partial<User>, oldUser: User | null): Promise<User> {
  162. const data: any = { user: {} }
  163. const oldData: any = oldUser || {}
  164. if (user.email || oldData.email) {
  165. data.user.email = user.email
  166. }
  167. if (user.description || oldData.description) {
  168. data.user.description = user.description
  169. }
  170. if (user.enabled != null) {
  171. data.user.enabled = user.enabled
  172. }
  173. if (user.name) {
  174. data.user.name = user.name
  175. }
  176. if (user.password) {
  177. data.user.password = user.password
  178. }
  179. if (user.project_id || oldData.project_id) {
  180. data.user.project_id = user.project_id
  181. }
  182. const response = await Api.send({
  183. url: `${configLoader.config.servicesUrls.keystone}/users/${userId}`,
  184. method: 'PATCH',
  185. data,
  186. })
  187. let updatedUser: User = response.data.user
  188. if (updatedUser.extra) {
  189. updatedUser = {
  190. ...updatedUser,
  191. ...updatedUser.extra,
  192. }
  193. }
  194. // if project id was updated, assign him to that project, if his not already assigned
  195. if (data.user.project_id) {
  196. const projects: Project[] = await this.getProjects(updatedUser.id)
  197. if (projects.find(p => p.id === data.user.project_id)) {
  198. return updatedUser
  199. }
  200. await this.assignUserToProject(updatedUser.id, updatedUser.project_id || 'undefined')
  201. return updatedUser
  202. }
  203. return updatedUser
  204. }
  205. async add(user: User): Promise<User> {
  206. const data: any = { user: {} }
  207. data.user.name = user.name
  208. data.user.password = user.password || ''
  209. data.user.enabled = user.enabled == null ? true : user.enabled
  210. if (user.email) {
  211. data.user.email = user.email
  212. }
  213. if (user.description) {
  214. data.user.description = user.description
  215. }
  216. if (user.project_id) {
  217. data.user.project_id = user.project_id
  218. }
  219. const response = await Api.send({
  220. url: `${configLoader.config.servicesUrls.keystone}/users`,
  221. method: 'POST',
  222. data,
  223. })
  224. let addedUser: User = response.data.user
  225. if (addedUser.extra) {
  226. addedUser = {
  227. ...addedUser,
  228. ...addedUser.extra,
  229. }
  230. }
  231. // If the user has a project id set, assign him to that project with admin role
  232. if (addedUser.project_id) {
  233. await this.assignUserToProject(addedUser.id, addedUser.project_id || 'undefined')
  234. return addedUser
  235. }
  236. return addedUser
  237. }
  238. async delete(userId: string): Promise<void> {
  239. await Api.send({
  240. url: `${configLoader.config.servicesUrls.keystone}/users/${userId}`,
  241. method: 'DELETE',
  242. })
  243. }
  244. async assignUserToProject(userId: string, projectId: string): Promise<void> {
  245. const roleId: string = await this.getMemberRoleId()
  246. await this.assignUserToProjectWithRole(userId, projectId, roleId)
  247. }
  248. async assignUserToProjectWithRole(
  249. userId: string, projectId: string, roleId: string,
  250. ): Promise<void> {
  251. await Api.send({
  252. url: `${configLoader.config.servicesUrls.keystone}/projects/${projectId}/users/${userId}/roles/${roleId}`,
  253. method: 'PUT',
  254. })
  255. }
  256. async getMemberRoleId(): Promise<string> {
  257. const roles: { id: string, name: string }[] = await this.getRoles()
  258. const role = roles.find(r => r.name === '_member_')
  259. const roleId = role ? role.id : ''
  260. return roleId
  261. }
  262. async getAdminRoleId(): Promise<string> {
  263. const roles: { id: string, name: string }[] = await this.getRoles()
  264. const role = roles.find(r => r.name.toLowerCase() === configLoader.config.adminRoleName)
  265. const roleId = role ? role.id : ''
  266. return roleId
  267. }
  268. async getRoles(): Promise<Role[]> {
  269. const response = await Api.get(`${configLoader.config.servicesUrls.keystone}/roles`)
  270. const roles: Role[] = response.data.roles
  271. roles.sort((r1, r2) => r1.name.localeCompare(r2.name))
  272. return roles
  273. }
  274. async getProjects(userId: string): Promise<Project[]> {
  275. const response = await Api.get(`${configLoader.config.servicesUrls.keystone}/role_assignments?include_names`)
  276. const assignments: RoleAssignment[] = response.data.role_assignments
  277. const projects: Project[] = assignments
  278. .filter(a => a.user.id === userId)
  279. .filter((a, i, arr) => arr
  280. .findIndex(e => e.scope.project
  281. && a.scope.project && e.scope.project.id === a.scope.project.id) === i)
  282. .map(a => a.scope.project)
  283. .filter(utils.notEmpty)
  284. return projects
  285. }
  286. async isAdmin(userId: string): Promise<boolean> {
  287. const response = await Api.send({
  288. url: `${configLoader.config.servicesUrls.keystone}/role_assignments?include_names`,
  289. quietError: true,
  290. })
  291. const roleAssignments: RoleAssignment[] = response.data.role_assignments
  292. return roleAssignments
  293. .filter(a => a && a.user && a.user.id === userId)
  294. .filter(a => a && a.role && a.role.name
  295. && a.role.name.toLowerCase() === configLoader.config.adminRoleName)
  296. .length > 0
  297. }
  298. }
  299. export default new UserSource()