/*
Copyright (C) 2017 Cloudbase Solutions SRL
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
import cookie from "js-cookie";
import Api from "@src/utils/ApiCaller";
import configLoader from "@src/utils/Config";
import type { Credentials, User } from "@src/@types/User";
import type { Role, Project, RoleAssignment } from "@src/@types/Project";
import utils from "@src/utils/ObjectUtils";
class UserModel {
static parseUserData(data: any) {
const newData = {
id: data.token.user.id,
name: data.token.user.name,
email: data.token.user.email,
project: data.token.project,
};
return newData;
}
}
class UserSource {
saveDomainName(domainName: string) {
localStorage.setItem("userDomainName", domainName);
}
get domainName(): string {
return configLoader.config.showUserDomainInput
? localStorage.getItem("userDomainName") ||
configLoader.config.defaultUserDomain
: configLoader.config.defaultUserDomain;
}
async login(userData: Credentials): Promise {
const auth = {
auth: {
identity: {
methods: ["password"],
password: {
user: {
name: userData.name,
domain: { name: userData.domain },
password: userData.password,
},
},
},
scope: "unscoped",
},
};
Api.setDefaultHeader("X-Auth-Token", null);
const response = await Api.send({
url: `${configLoader.config.servicesUrls.keystone}/auth/tokens`,
method: "POST",
data: auth,
});
const token = response.headers
? response.headers["X-Subject-Token"] ||
response.headers["x-subject-token"]
: "";
Api.setDefaultHeader("X-Auth-Token", token);
cookie.set("unscopedToken", token, { expires: 30 });
return response.data;
}
async loginScoped(projectId: string, skipCookie?: boolean): Promise {
const useProjectId = skipCookie
? projectId
: cookie.get("projectId") || projectId;
const token = cookie.get("unscopedToken");
const auth = {
auth: {
identity: {
methods: ["token"],
token: {
id: token,
},
},
scope: {
project: {
id: useProjectId,
},
},
},
};
Api.setDefaultHeader("X-Auth-Token", null);
try {
const response = await Api.send({
url: `${configLoader.config.servicesUrls.keystone}/auth/tokens`,
method: "POST",
data: auth,
});
const subjectToken = response.headers
? response.headers["X-Subject-Token"] ||
response.headers["x-subject-token"]
: "";
let data: User = UserModel.parseUserData(response.data);
data = { ...data, token: subjectToken };
cookie.set("token", data.token || "", { expires: 30 });
cookie.set("projectId", data.project.id, { expires: 30 });
Api.setDefaultHeader("X-Auth-Token", data.token || "");
return data;
} catch (err) {
if (!skipCookie) {
const user: User = await this.loginScoped(projectId, true);
return user;
}
throw err;
}
}
async tokenLogin(): Promise {
const token = cookie.get("token") || "";
if (token) {
Api.setDefaultHeader("X-Auth-Token", token);
}
try {
const response = await Api.send({
url: `${configLoader.config.servicesUrls.keystone}/auth/tokens`,
headers: { "X-Subject-Token": token },
});
let data: User = UserModel.parseUserData(response.data);
data = { ...data, token };
return data;
} catch (err) {
cookie.remove("token");
Api.setDefaultHeader("X-Auth-Token", null);
throw err;
}
}
async switchProject(): Promise {
const token = cookie.get("unscopedToken");
if (token) {
cookie.remove("projectId");
return;
}
throw new Error("No unscoped token");
}
async logout(): Promise {
const token = cookie.get("token");
const clear = () => {
cookie.remove("token");
window.location.href = "/login";
Api.setDefaultHeader("X-Auth-Token", null);
};
try {
await Api.send({
url: `${configLoader.config.servicesUrls.keystone}/auth/tokens`,
method: "DELETE",
headers: { "X-Subject-Token": token || "" },
});
} finally {
clear();
}
}
async getUserInfo(userId: string): Promise {
const response = await Api.get(
`${configLoader.config.servicesUrls.keystone}/users/${userId}`
);
return response.data.user;
}
async getAllUsers(skipLog?: boolean, quietError?: boolean): Promise {
const response = await Api.send({
url: `${configLoader.config.servicesUrls.keystone}/users`,
skipLog,
quietError,
});
let users: User[] = response.data.users;
await utils.waitFor(() => Boolean(configLoader.config));
users = users
.filter(u => !configLoader.config.hiddenUsers.find(hu => hu === u.name))
.sort((u1, u2) => u1.name.localeCompare(u2.name));
return users;
}
async update(
userId: string,
user: Partial,
oldUser: User | null
): Promise {
const data: any = { user: {} };
const oldData: any = oldUser || {};
if (user.email || oldData.email) {
data.user.email = user.email;
}
if (user.description || oldData.description) {
data.user.description = user.description;
}
if (user.enabled != null) {
data.user.enabled = user.enabled;
}
if (user.name) {
data.user.name = user.name;
}
if (user.password) {
data.user.password = user.password;
}
if (user.project_id || oldData.project_id) {
data.user.project_id = user.project_id;
}
const response = await Api.send({
url: `${configLoader.config.servicesUrls.keystone}/users/${userId}`,
method: "PATCH",
data,
});
let updatedUser: User = response.data.user;
if (updatedUser.extra) {
updatedUser = {
...updatedUser,
...updatedUser.extra,
};
}
// if project id was updated, assign him to that project, if his not already assigned
if (data.user.project_id) {
const projects: Project[] = await this.getProjects(updatedUser.id);
if (projects.find(p => p.id === data.user.project_id)) {
return updatedUser;
}
await this.assignUserToProject(
updatedUser.id,
updatedUser.project_id || "undefined"
);
return updatedUser;
}
return updatedUser;
}
async add(user: User): Promise {
const data: any = { user: {} };
data.user.name = user.name;
data.user.password = user.password || "";
data.user.enabled = user.enabled == null ? true : user.enabled;
if (user.email) {
data.user.email = user.email;
}
if (user.description) {
data.user.description = user.description;
}
if (user.project_id) {
data.user.project_id = user.project_id;
}
const response = await Api.send({
url: `${configLoader.config.servicesUrls.keystone}/users`,
method: "POST",
data,
});
let addedUser: User = response.data.user;
if (addedUser.extra) {
addedUser = {
...addedUser,
...addedUser.extra,
};
}
// If the user has a project id set, assign him to that project with admin role
if (addedUser.project_id) {
await this.assignUserToProject(
addedUser.id,
addedUser.project_id || "undefined"
);
return addedUser;
}
return addedUser;
}
async delete(userId: string): Promise {
await Api.send({
url: `${configLoader.config.servicesUrls.keystone}/users/${userId}`,
method: "DELETE",
});
}
async assignUserToProject(userId: string, projectId: string): Promise {
const roleId: string = await this.getMemberRoleId();
await this.assignUserToProjectWithRole(userId, projectId, roleId);
}
async assignUserToProjectWithRole(
userId: string,
projectId: string,
roleId: string
): Promise {
await Api.send({
url: `${configLoader.config.servicesUrls.keystone}/projects/${projectId}/users/${userId}/roles/${roleId}`,
method: "PUT",
});
}
async getMemberRoleId(): Promise {
const roles: { id: string; name: string }[] = await this.getRoles();
const role = roles.find(r => r.name === "_member_");
const roleId = role ? role.id : "";
return roleId;
}
async getAdminRoleId(): Promise {
const roles: { id: string; name: string }[] = await this.getRoles();
const role = roles.find(
r => r.name.toLowerCase() === configLoader.config.adminRoleName
);
const roleId = role ? role.id : "";
return roleId;
}
async getRoles(): Promise {
const response = await Api.get(
`${configLoader.config.servicesUrls.keystone}/roles`
);
const roles: Role[] = response.data.roles;
roles.sort((r1, r2) => r1.name.localeCompare(r2.name));
return roles;
}
async getProjects(userId: string): Promise {
const response = await Api.get(
`${configLoader.config.servicesUrls.keystone}/role_assignments?include_names`
);
const assignments: RoleAssignment[] = response.data.role_assignments;
const projects: Project[] = assignments
.filter(a => a.user.id === userId)
.filter(
(a, i, arr) =>
arr.findIndex(
e =>
e.scope.project &&
a.scope.project &&
e.scope.project.id === a.scope.project.id
) === i
)
.map(a => a.scope.project)
.filter(utils.notEmpty);
return projects;
}
async isAdmin(userId: string): Promise {
const response = await Api.send({
url: `${configLoader.config.servicesUrls.keystone}/role_assignments?include_names`,
quietError: true,
});
const roleAssignments: RoleAssignment[] = response.data.role_assignments;
return (
roleAssignments
.filter(a => a && a.user && a.user.id === userId)
.filter(
a =>
a &&
a.role &&
a.role.name &&
a.role.name.toLowerCase() === configLoader.config.adminRoleName
).length > 0
);
}
}
export default new UserSource();