bitwardenjavascripttypescriptangularelectronclidesktopnodejswebextensionfirefoxbrowser-extensionchromesafari
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
254 lines
9.5 KiB
254 lines
9.5 KiB
import { firstValueFrom } from "rxjs"; |
|
|
|
import { ApiService } from "@bitwarden/common/abstractions/api.service"; |
|
import { CipherService } from "@bitwarden/common/abstractions/cipher.service"; |
|
import { CollectionService } from "@bitwarden/common/abstractions/collection.service"; |
|
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction"; |
|
import { OrganizationService } from "@bitwarden/common/abstractions/organization.service"; |
|
import { SearchService } from "@bitwarden/common/abstractions/search.service"; |
|
import { Utils } from "@bitwarden/common/misc/utils"; |
|
import { CollectionData } from "@bitwarden/common/models/data/collectionData"; |
|
import { Collection } from "@bitwarden/common/models/domain/collection"; |
|
import { |
|
CollectionDetailsResponse as ApiCollectionDetailsResponse, |
|
CollectionResponse as ApiCollectionResponse, |
|
} from "@bitwarden/common/models/response/collectionResponse"; |
|
import { ListResponse as ApiListResponse } from "@bitwarden/common/models/response/listResponse"; |
|
import { CipherView } from "@bitwarden/common/models/view/cipherView"; |
|
import { Response } from "@bitwarden/node/cli/models/response"; |
|
import { ListResponse } from "@bitwarden/node/cli/models/response/listResponse"; |
|
|
|
import { CipherResponse } from "../models/response/cipherResponse"; |
|
import { CollectionResponse } from "../models/response/collectionResponse"; |
|
import { FolderResponse } from "../models/response/folderResponse"; |
|
import { OrganizationResponse } from "../models/response/organizationResponse"; |
|
import { OrganizationUserResponse } from "../models/response/organizationUserResponse"; |
|
import { CliUtils } from "../utils"; |
|
|
|
export class ListCommand { |
|
constructor( |
|
private cipherService: CipherService, |
|
private folderService: FolderService, |
|
private collectionService: CollectionService, |
|
private organizationService: OrganizationService, |
|
private searchService: SearchService, |
|
private apiService: ApiService |
|
) {} |
|
|
|
async run(object: string, cmdOptions: Record<string, any>): Promise<Response> { |
|
const normalizedOptions = new Options(cmdOptions); |
|
switch (object.toLowerCase()) { |
|
case "items": |
|
return await this.listCiphers(normalizedOptions); |
|
case "folders": |
|
return await this.listFolders(normalizedOptions); |
|
case "collections": |
|
return await this.listCollections(normalizedOptions); |
|
case "org-collections": |
|
return await this.listOrganizationCollections(normalizedOptions); |
|
case "org-members": |
|
return await this.listOrganizationMembers(normalizedOptions); |
|
case "organizations": |
|
return await this.listOrganizations(normalizedOptions); |
|
default: |
|
return Response.badRequest("Unknown object."); |
|
} |
|
} |
|
|
|
private async listCiphers(options: Options) { |
|
let ciphers: CipherView[]; |
|
options.trash = options.trash || false; |
|
if (options.url != null && options.url.trim() !== "") { |
|
ciphers = await this.cipherService.getAllDecryptedForUrl(options.url); |
|
} else { |
|
ciphers = await this.cipherService.getAllDecrypted(); |
|
} |
|
|
|
if ( |
|
options.folderId != null || |
|
options.collectionId != null || |
|
options.organizationId != null |
|
) { |
|
ciphers = ciphers.filter((c) => { |
|
if (options.trash !== c.isDeleted) { |
|
return false; |
|
} |
|
if (options.folderId != null) { |
|
if (options.folderId === "notnull" && c.folderId != null) { |
|
return true; |
|
} |
|
const folderId = options.folderId === "null" ? null : options.folderId; |
|
if (folderId === c.folderId) { |
|
return true; |
|
} |
|
} |
|
|
|
if (options.organizationId != null) { |
|
if (options.organizationId === "notnull" && c.organizationId != null) { |
|
return true; |
|
} |
|
const organizationId = options.organizationId === "null" ? null : options.organizationId; |
|
if (organizationId === c.organizationId) { |
|
return true; |
|
} |
|
} |
|
|
|
if (options.collectionId != null) { |
|
if ( |
|
options.collectionId === "notnull" && |
|
c.collectionIds != null && |
|
c.collectionIds.length > 0 |
|
) { |
|
return true; |
|
} |
|
const collectionId = options.collectionId === "null" ? null : options.collectionId; |
|
if (collectionId == null && (c.collectionIds == null || c.collectionIds.length === 0)) { |
|
return true; |
|
} |
|
if ( |
|
collectionId != null && |
|
c.collectionIds != null && |
|
c.collectionIds.indexOf(collectionId) > -1 |
|
) { |
|
return true; |
|
} |
|
} |
|
return false; |
|
}); |
|
} else if (options.search == null || options.search.trim() === "") { |
|
ciphers = ciphers.filter((c) => options.trash === c.isDeleted); |
|
} |
|
|
|
if (options.search != null && options.search.trim() !== "") { |
|
ciphers = this.searchService.searchCiphersBasic(ciphers, options.search, options.trash); |
|
} |
|
|
|
const res = new ListResponse(ciphers.map((o) => new CipherResponse(o))); |
|
return Response.success(res); |
|
} |
|
|
|
private async listFolders(options: Options) { |
|
let folders = await firstValueFrom(this.folderService.folderViews$); |
|
|
|
if (options.search != null && options.search.trim() !== "") { |
|
folders = CliUtils.searchFolders(folders, options.search); |
|
} |
|
|
|
const res = new ListResponse(folders.map((o) => new FolderResponse(o))); |
|
return Response.success(res); |
|
} |
|
|
|
private async listCollections(options: Options) { |
|
let collections = await this.collectionService.getAllDecrypted(); |
|
|
|
if (options.organizationId != null) { |
|
collections = collections.filter((c) => { |
|
if (options.organizationId === c.organizationId) { |
|
return true; |
|
} |
|
return false; |
|
}); |
|
} |
|
|
|
if (options.search != null && options.search.trim() !== "") { |
|
collections = CliUtils.searchCollections(collections, options.search); |
|
} |
|
|
|
const res = new ListResponse(collections.map((o) => new CollectionResponse(o))); |
|
return Response.success(res); |
|
} |
|
|
|
private async listOrganizationCollections(options: Options) { |
|
if (options.organizationId == null || options.organizationId === "") { |
|
return Response.badRequest("`organizationid` option is required."); |
|
} |
|
if (!Utils.isGuid(options.organizationId)) { |
|
return Response.badRequest("`" + options.organizationId + "` is not a GUID."); |
|
} |
|
const organization = await this.organizationService.get(options.organizationId); |
|
if (organization == null) { |
|
return Response.error("Organization not found."); |
|
} |
|
|
|
try { |
|
let response: ApiListResponse<ApiCollectionResponse>; |
|
if (organization.canViewAllCollections) { |
|
response = await this.apiService.getCollections(options.organizationId); |
|
} else { |
|
response = await this.apiService.getUserCollections(); |
|
} |
|
const collections = response.data |
|
.filter((c) => c.organizationId === options.organizationId) |
|
.map((r) => new Collection(new CollectionData(r as ApiCollectionDetailsResponse))); |
|
let decCollections = await this.collectionService.decryptMany(collections); |
|
if (options.search != null && options.search.trim() !== "") { |
|
decCollections = CliUtils.searchCollections(decCollections, options.search); |
|
} |
|
const res = new ListResponse(decCollections.map((o) => new CollectionResponse(o))); |
|
return Response.success(res); |
|
} catch (e) { |
|
return Response.error(e); |
|
} |
|
} |
|
|
|
private async listOrganizationMembers(options: Options) { |
|
if (options.organizationId == null || options.organizationId === "") { |
|
return Response.badRequest("`organizationid` option is required."); |
|
} |
|
if (!Utils.isGuid(options.organizationId)) { |
|
return Response.badRequest("`" + options.organizationId + "` is not a GUID."); |
|
} |
|
const organization = await this.organizationService.get(options.organizationId); |
|
if (organization == null) { |
|
return Response.error("Organization not found."); |
|
} |
|
|
|
try { |
|
const response = await this.apiService.getOrganizationUsers(options.organizationId); |
|
const res = new ListResponse( |
|
response.data.map((r) => { |
|
const u = new OrganizationUserResponse(); |
|
u.email = r.email; |
|
u.name = r.name; |
|
u.id = r.id; |
|
u.status = r.status; |
|
u.type = r.type; |
|
u.twoFactorEnabled = r.twoFactorEnabled; |
|
return u; |
|
}) |
|
); |
|
return Response.success(res); |
|
} catch (e) { |
|
return Response.error(e); |
|
} |
|
} |
|
|
|
private async listOrganizations(options: Options) { |
|
let organizations = await this.organizationService.getAll(); |
|
|
|
if (options.search != null && options.search.trim() !== "") { |
|
organizations = CliUtils.searchOrganizations(organizations, options.search); |
|
} |
|
|
|
const res = new ListResponse(organizations.map((o) => new OrganizationResponse(o))); |
|
return Response.success(res); |
|
} |
|
} |
|
|
|
class Options { |
|
organizationId: string; |
|
collectionId: string; |
|
folderId: string; |
|
search: string; |
|
url: string; |
|
trash: boolean; |
|
|
|
constructor(passedOptions: Record<string, any>) { |
|
this.organizationId = passedOptions?.organizationid || passedOptions?.organizationId; |
|
this.collectionId = passedOptions?.collectionid || passedOptions?.collectionId; |
|
this.folderId = passedOptions?.folderid || passedOptions?.folderId; |
|
this.search = passedOptions?.search; |
|
this.url = passedOptions?.url; |
|
this.trash = CliUtils.convertBooleanOption(passedOptions?.trash); |
|
} |
|
}
|
|
|