diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 97b43a21..af81eb73 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,8 +19,8 @@ jobs: name: Setup runs-on: ubuntu-20.04 outputs: - release_version: ${{ steps.version.outputs.package }} - tag_version: ${{ steps.version.outputs.tag }} + release_version: ${{ steps.version.outputs.version }} + tag_version: ${{ steps.version.outputs.version }} branch_name: ${{ steps.branch.outputs.branch_name }} steps: - name: Branch check @@ -38,20 +38,11 @@ jobs: - name: Check Release Version id: version - run: | - version=$( jq -r ".version" package.json) - previous_release_tag_version=$( - curl -sL https://api.github.com/repos/$GITHUB_REPOSITORY/releases/latest | jq -r ".tag_name" - ) - - if [ "v$version" == "$previous_release_tag_version" ] && \ - [ "${{ github.event.inputs.release_type }}" == "Initial Release" ]; then - echo "[!] Already released v$version. Please bump version to continue" - exit 1 - fi - - echo "::set-output name=package::$version" - echo "::set-output name=tag::v$version" + uses: bitwarden/gh-actions/release-version-check@ea9fab01d76940267b4147cc1c4542431246b9f6 + with: + release-type: ${{ github.event.inputs.release_type }} + project-type: ts + file: package.json - name: Get branch name id: branch diff --git a/bitwarden_license/src/app/organizations/organizations-routing.module.ts b/bitwarden_license/src/app/organizations/organizations-routing.module.ts index 7ce7f56c..691feda3 100644 --- a/bitwarden_license/src/app/organizations/organizations-routing.module.ts +++ b/bitwarden_license/src/app/organizations/organizations-routing.module.ts @@ -22,14 +22,16 @@ const routes: Routes = [ component: ManageComponent, canActivate: [PermissionsGuard], data: { - permissions: [ - NavigationPermissionsService.getPermissions("manage").concat(Permissions.ManageSso), - ], + permissions: NavigationPermissionsService.getPermissions("manage"), }, children: [ { path: "sso", component: SsoComponent, + canActivate: [PermissionsGuard], + data: { + permissions: [Permissions.ManageSso], + }, }, ], }, diff --git a/jslib b/jslib index 1370006f..0d658ba2 160000 --- a/jslib +++ b/jslib @@ -1 +1 @@ -Subproject commit 1370006f6ea310cf85a12bcbd8213f74f9552c4d +Subproject commit 0d658ba26dfb539f15e33f396e597e6727070bc2 diff --git a/package-lock.json b/package-lock.json index 1106cb2e..8e689363 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bitwarden/web-vault", - "version": "2.28.1", + "version": "2022.05.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@bitwarden/web-vault", - "version": "2.28.1", + "version": "2022.05.0", "hasInstallScript": true, "license": "GPL-3.0", "dependencies": { diff --git a/package.json b/package.json index 29da7318..9fe29fcd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/web-vault", - "version": "2.28.1", + "version": "2022.05.0", "license": "GPL-3.0", "repository": "https://github.com/bitwarden/web", "scripts": { diff --git a/src/app/accounts/lock.component.ts b/src/app/accounts/lock.component.ts index 4b8a0d4b..79148100 100644 --- a/src/app/accounts/lock.component.ts +++ b/src/app/accounts/lock.component.ts @@ -55,7 +55,7 @@ export class LockComponent extends BaseLockComponent { await super.ngOnInit(); this.onSuccessfulSubmit = async () => { const previousUrl = this.routerService.getPreviousUrl(); - if (previousUrl !== "/" && previousUrl.indexOf("lock") === -1) { + if (previousUrl && previousUrl !== "/" && previousUrl.indexOf("lock") === -1) { this.successRoute = previousUrl; } this.router.navigateByUrl(this.successRoute); diff --git a/src/app/accounts/login.component.ts b/src/app/accounts/login.component.ts index 21a1f6d0..524137a6 100644 --- a/src/app/accounts/login.component.ts +++ b/src/app/accounts/login.component.ts @@ -74,7 +74,7 @@ export class LoginComponent extends BaseLoginComponent { if (qParams.premium != null) { this.routerService.setPreviousUrl("/settings/premium"); } else if (qParams.org != null) { - const route = this.router.createUrlTree(["settings/create-organization"], { + const route = this.router.createUrlTree(["create-organization"], { queryParams: { plan: qParams.org }, }); this.routerService.setPreviousUrl(route.toString()); diff --git a/src/app/accounts/register.component.html b/src/app/accounts/register.component.html index d86e0a43..970c2c13 100644 --- a/src/app/accounts/register.component.html +++ b/src/app/accounts/register.component.html @@ -101,7 +101,7 @@
-

{{ "createAccount" | i18n }}

+

{{ "createAccount" | i18n }}

-
© {{ year }}, Bitwarden Inc.
+
© {{ year }} Bitwarden Inc.
{{ "versionNumber" | i18n: version }} diff --git a/src/app/layouts/frontend-layout.component.html b/src/app/layouts/frontend-layout.component.html index 479302d3..d737bc9f 100644 --- a/src/app/layouts/frontend-layout.component.html +++ b/src/app/layouts/frontend-layout.component.html @@ -1,5 +1,5 @@
- © {{ year }}, Bitwarden Inc.
+ © {{ year }} Bitwarden Inc.
{{ "versionNumber" | i18n: version }}
diff --git a/src/app/layouts/navbar.component.ts b/src/app/layouts/navbar.component.ts index 41585ef4..d93ec65d 100644 --- a/src/app/layouts/navbar.component.ts +++ b/src/app/layouts/navbar.component.ts @@ -1,5 +1,6 @@ -import { Component, OnInit } from "@angular/core"; +import { Component, NgZone, OnInit } from "@angular/core"; +import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service"; import { I18nService } from "jslib-common/abstractions/i18n.service"; import { MessagingService } from "jslib-common/abstractions/messaging.service"; import { OrganizationService } from "jslib-common/abstractions/organization.service"; @@ -31,7 +32,9 @@ export class NavbarComponent implements OnInit { private providerService: ProviderService, private syncService: SyncService, private organizationService: OrganizationService, - private i18nService: I18nService + private i18nService: I18nService, + private broadcasterService: BroadcasterService, + private ngZone: NgZone ) { this.selfHosted = this.platformUtilsService.isSelfHost(); } @@ -49,8 +52,24 @@ export class NavbarComponent implements OnInit { } this.providers = await this.providerService.getAll(); + this.organizations = await this.buildOrganizations(); + + this.broadcasterService.subscribe(this.constructor.name, async (message: any) => { + this.ngZone.run(async () => { + switch (message.command) { + case "organizationCreated": + if (this.organizations.length < 1) { + this.organizations = await this.buildOrganizations(); + } + break; + } + }); + }); + } + + async buildOrganizations() { const allOrgs = await this.organizationService.getAll(); - this.organizations = allOrgs + return allOrgs .filter((org) => OrgNavigationPermissionsService.canAccessAdmin(org)) .sort(Utils.getSortFunction(this.i18nService, "name")); } diff --git a/src/app/modules/loose-components.module.ts b/src/app/modules/loose-components.module.ts index 62113d28..a10877e7 100644 --- a/src/app/modules/loose-components.module.ts +++ b/src/app/modules/loose-components.module.ts @@ -58,7 +58,6 @@ import { EmergencyAccessTakeoverComponent } from "../settings/emergency-access-t import { EmergencyAccessViewComponent } from "../settings/emergency-access-view.component"; import { EmergencyAccessComponent } from "../settings/emergency-access.component"; import { EmergencyAddEditComponent } from "../settings/emergency-add-edit.component"; -import { LinkSsoComponent } from "../settings/link-sso.component"; import { PaymentMethodComponent } from "../settings/payment-method.component"; import { PreferencesComponent } from "../settings/preferences.component"; import { PremiumComponent } from "../settings/premium.component"; @@ -155,7 +154,6 @@ import { OrganizationBadgeModule } from "./vault/modules/organization-badge/orga HintComponent, ImportComponent, InactiveTwoFactorReportComponent, - LinkSsoComponent, LockComponent, LoginComponent, NestedCheckboxComponent, @@ -258,7 +256,6 @@ import { OrganizationBadgeModule } from "./vault/modules/organization-badge/orga HintComponent, ImportComponent, InactiveTwoFactorReportComponent, - LinkSsoComponent, LockComponent, LoginComponent, NestedCheckboxComponent, diff --git a/src/app/organizations/manage/entity-users.component.html b/src/app/modules/organizations/manage/entity-users.component.html similarity index 74% rename from src/app/organizations/manage/entity-users.component.html rename to src/app/modules/organizations/manage/entity-users.component.html index beb9ff0a..f4c157a9 100644 --- a/src/app/organizations/manage/entity-users.component.html +++ b/src/app/modules/organizations/manage/entity-users.component.html @@ -29,52 +29,52 @@ > {{ "loading" | i18n }}
- +
diff --git a/src/app/modules/organizations/users/enroll-master-password-reset.component.ts b/src/app/modules/organizations/users/enroll-master-password-reset.component.ts new file mode 100644 index 00000000..b996493d --- /dev/null +++ b/src/app/modules/organizations/users/enroll-master-password-reset.component.ts @@ -0,0 +1,97 @@ +import { Component } from "@angular/core"; + +import { ModalRef } from "jslib-angular/components/modal/modal.ref"; +import { ModalConfig } from "jslib-angular/services/modal.service"; +import { ApiService } from "jslib-common/abstractions/api.service"; +import { CryptoService } from "jslib-common/abstractions/crypto.service"; +import { I18nService } from "jslib-common/abstractions/i18n.service"; +import { LogService } from "jslib-common/abstractions/log.service"; +import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; +import { SyncService } from "jslib-common/abstractions/sync.service"; +import { UserVerificationService } from "jslib-common/abstractions/userVerification.service"; +import { Utils } from "jslib-common/misc/utils"; +import { Organization } from "jslib-common/models/domain/organization"; +import { OrganizationUserResetPasswordEnrollmentRequest } from "jslib-common/models/request/organizationUserResetPasswordEnrollmentRequest"; +import { Verification } from "jslib-common/types/verification"; + +@Component({ + selector: "app-enroll-master-password-reset", + templateUrl: "enroll-master-password-reset.component.html", +}) +export class EnrollMasterPasswordReset { + organization: Organization; + + verification: Verification; + formPromise: Promise; + + constructor( + private userVerificationService: UserVerificationService, + private apiService: ApiService, + private platformUtilsService: PlatformUtilsService, + private i18nService: I18nService, + private cryptoService: CryptoService, + private syncService: SyncService, + private logService: LogService, + private modalRef: ModalRef, + config: ModalConfig + ) { + this.organization = config.data.organization; + } + + async submit() { + let toastStringRef = "withdrawPasswordResetSuccess"; + + this.formPromise = this.userVerificationService + .buildRequest(this.verification, OrganizationUserResetPasswordEnrollmentRequest) + .then(async (request) => { + // Set variables + let keyString: string = null; + + // Enrolling + if (!this.organization.resetPasswordEnrolled) { + // Retrieve Public Key + const orgKeys = await this.apiService.getOrganizationKeys(this.organization.id); + if (orgKeys == null) { + throw new Error(this.i18nService.t("resetPasswordOrgKeysError")); + } + + const publicKey = Utils.fromB64ToArray(orgKeys.publicKey); + + // RSA Encrypt user's encKey.key with organization public key + const encKey = await this.cryptoService.getEncKey(); + const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey.buffer); + keyString = encryptedKey.encryptedString; + toastStringRef = "enrollPasswordResetSuccess"; + + // Create request and execute enrollment + request.resetPasswordKey = keyString; + await this.apiService.putOrganizationUserResetPasswordEnrollment( + this.organization.id, + this.organization.userId, + request + ); + } else { + // Withdrawal + request.resetPasswordKey = keyString; + await this.apiService.putOrganizationUserResetPasswordEnrollment( + this.organization.id, + this.organization.userId, + request + ); + } + + await this.syncService.fullSync(true); + }); + try { + await this.formPromise; + this.platformUtilsService.showToast("success", null, this.i18nService.t(toastStringRef)); + this.modalRef.close(); + } catch (e) { + this.logService.error(e); + } + } + + get isEnrolled(): boolean { + return this.organization.resetPasswordEnrolled; + } +} diff --git a/src/app/modules/organizations/users/organization-user.module.ts b/src/app/modules/organizations/users/organization-user.module.ts new file mode 100644 index 00000000..aed0ac66 --- /dev/null +++ b/src/app/modules/organizations/users/organization-user.module.ts @@ -0,0 +1,14 @@ +import { ScrollingModule } from "@angular/cdk/scrolling"; +import { NgModule } from "@angular/core"; + +import { LooseComponentsModule } from "../../loose-components.module"; +import { SharedModule } from "../../shared.module"; + +import { EnrollMasterPasswordReset } from "./enroll-master-password-reset.component"; + +@NgModule({ + imports: [SharedModule, ScrollingModule, LooseComponentsModule], + declarations: [EnrollMasterPasswordReset], + exports: [EnrollMasterPasswordReset], +}) +export class OrganizationUserModule {} diff --git a/src/app/modules/vault-filter/components/collection-filter.component.html b/src/app/modules/vault-filter/components/collection-filter.component.html index 936feb2f..24463dca 100644 --- a/src/app/modules/vault-filter/components/collection-filter.component.html +++ b/src/app/modules/vault-filter/components/collection-filter.component.html @@ -16,7 +16,7 @@ aria-hidden="true" > -

{{ collectionsGrouping.name | i18n }}

+

 {{ collectionsGrouping.name | i18n }}

- +
- - -
diff --git a/src/app/modules/vault-filter/components/organization-options.component.ts b/src/app/modules/vault-filter/components/organization-options.component.ts index 1e787418..fb256acd 100644 --- a/src/app/modules/vault-filter/components/organization-options.component.ts +++ b/src/app/modules/vault-filter/components/organization-options.component.ts @@ -1,17 +1,17 @@ import { Component, Input } from "@angular/core"; +import { ModalService } from "jslib-angular/services/modal.service"; import { ApiService } from "jslib-common/abstractions/api.service"; -import { CryptoService } from "jslib-common/abstractions/crypto.service"; import { I18nService } from "jslib-common/abstractions/i18n.service"; import { LogService } from "jslib-common/abstractions/log.service"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; import { PolicyService } from "jslib-common/abstractions/policy.service"; import { SyncService } from "jslib-common/abstractions/sync.service"; import { PolicyType } from "jslib-common/enums/policyType"; -import { Utils } from "jslib-common/misc/utils"; import { Organization } from "jslib-common/models/domain/organization"; import { Policy } from "jslib-common/models/domain/policy"; -import { OrganizationUserResetPasswordEnrollmentRequest } from "jslib-common/models/request/organizationUserResetPasswordEnrollmentRequest"; + +import { EnrollMasterPasswordReset } from "../../organizations/users/enroll-master-password-reset.component"; @Component({ selector: "app-organization-options", @@ -29,8 +29,8 @@ export class OrganizationOptionsComponent { private i18nService: I18nService, private apiService: ApiService, private syncService: SyncService, - private cryptoService: CryptoService, private policyService: PolicyService, + private modalService: ModalService, private logService: LogService ) {} @@ -82,6 +82,7 @@ export class OrganizationOptionsComponent { this.platformUtilsService.showToast("success", null, "Unlinked SSO"); await this.load(); } catch (e) { + this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), e.message); this.logService.error(e); } } @@ -106,74 +107,17 @@ export class OrganizationOptionsComponent { this.platformUtilsService.showToast("success", null, this.i18nService.t("leftOrganization")); await this.load(); } catch (e) { + this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), e.message); this.logService.error(e); } } async toggleResetPasswordEnrollment(org: Organization) { - // Set variables - let keyString: string = null; - let toastStringRef = "withdrawPasswordResetSuccess"; - - // Enrolling - if (!org.resetPasswordEnrolled) { - // Alert user about enrollment - const confirmed = await this.platformUtilsService.showDialog( - this.i18nService.t("resetPasswordEnrollmentWarning"), - null, - this.i18nService.t("yes"), - this.i18nService.t("no"), - "warning" - ); - if (!confirmed) { - return; - } - - // Retrieve Public Key - this.actionPromise = this.apiService - .getOrganizationKeys(org.id) - .then(async (response) => { - if (response == null) { - throw new Error(this.i18nService.t("resetPasswordOrgKeysError")); - } - - const publicKey = Utils.fromB64ToArray(response.publicKey); - - // RSA Encrypt user's encKey.key with organization public key - const encKey = await this.cryptoService.getEncKey(); - const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey.buffer); - keyString = encryptedKey.encryptedString; - toastStringRef = "enrollPasswordResetSuccess"; - - // Create request and execute enrollment - const request = new OrganizationUserResetPasswordEnrollmentRequest(); - request.resetPasswordKey = keyString; - return this.apiService.putOrganizationUserResetPasswordEnrollment( - org.id, - org.userId, - request - ); - }) - .then(() => { - return this.syncService.fullSync(true); - }); - } else { - // Withdrawal - const request = new OrganizationUserResetPasswordEnrollmentRequest(); - request.resetPasswordKey = keyString; - this.actionPromise = this.apiService - .putOrganizationUserResetPasswordEnrollment(org.id, org.userId, request) - .then(() => { - return this.syncService.fullSync(true); - }); - } - - try { - await this.actionPromise; - this.platformUtilsService.showToast("success", null, this.i18nService.t(toastStringRef)); - await this.load(); - } catch (e) { - this.logService.error(e); - } + this.modalService.open(EnrollMasterPasswordReset, { + allowMultipleModals: true, + data: { + organization: org, + }, + }); } } diff --git a/src/app/modules/vault-filter/components/type-filter.component.html b/src/app/modules/vault-filter/components/type-filter.component.html index ff32894b..1149b0e9 100644 --- a/src/app/modules/vault-filter/components/type-filter.component.html +++ b/src/app/modules/vault-filter/components/type-filter.component.html @@ -15,9 +15,7 @@ }" > -

- {{ "types" | i18n }} -

+

 {{ "types" | i18n }}

- + {{ trashCleanupWarning }}

{{ "premiumUpgradeUnlockFeatures" | i18n }}

- + {{ "goPremium" | i18n }}
diff --git a/src/app/modules/vault/modules/individual-vault/individual-vault.component.ts b/src/app/modules/vault/modules/individual-vault/individual-vault.component.ts index c1bf9b51..ababbd8a 100644 --- a/src/app/modules/vault/modules/individual-vault/individual-vault.component.ts +++ b/src/app/modules/vault/modules/individual-vault/individual-vault.component.ts @@ -58,7 +58,6 @@ export class IndividualVaultComponent implements OnInit, OnDestroy { updateKeyModalRef: ViewContainerRef; favorites = false; - type: CipherType = null; folderId: string = null; collectionId: string = null; organizationId: string = null; @@ -209,7 +208,7 @@ export class IndividualVaultComponent implements OnInit, OnDestroy { cipherPassesFilter = cipher.type === this.activeFilter.cipherType; } if ( - this.activeFilter.selectedFolderId != null && + this.activeFilter.selectedFolder && this.activeFilter.selectedFolderId != "none" && cipherPassesFilter ) { @@ -327,7 +326,7 @@ export class IndividualVaultComponent implements OnInit, OnDestroy { async addCipher() { const component = await this.editCipher(null); - component.type = this.type; + component.type = this.activeFilter.cipherType; component.folderId = this.folderId === "none" ? null : this.folderId; if (this.activeFilter.selectedCollectionId != null) { const collection = this.filterComponent.collections.fullList.filter( @@ -399,7 +398,7 @@ export class IndividualVaultComponent implements OnInit, OnDestroy { if (queryParams == null) { queryParams = { favorites: this.favorites ? true : null, - type: this.type, + type: this.activeFilter.cipherType, folderId: this.folderId, collectionId: this.collectionId, deleted: this.deleted ? true : null, diff --git a/src/app/modules/vault/modules/organization-vault/organization-vault.component.ts b/src/app/modules/vault/modules/organization-vault/organization-vault.component.ts index 44b4d0f2..9dd3d945 100644 --- a/src/app/modules/vault/modules/organization-vault/organization-vault.component.ts +++ b/src/app/modules/vault/modules/organization-vault/organization-vault.component.ts @@ -124,7 +124,11 @@ export class OrganizationVaultComponent implements OnInit, OnDestroy { this.route.queryParams.subscribe(async (params) => { if (params.cipherId) { - if ((await this.cipherService.get(params.cipherId)) != null) { + if ( + // Handle users with implicit collection access since they use the admin endpoint + this.organization.canEditAnyCollection || + (await this.cipherService.get(params.cipherId)) != null + ) { this.editCipherId(params.cipherId); } else { this.platformUtilsService.showToast( @@ -169,7 +173,7 @@ export class OrganizationVaultComponent implements OnInit, OnDestroy { cipherPassesFilter = cipher.type === this.activeFilter.cipherType; } if ( - this.activeFilter.selectedFolderId != null && + this.activeFilter.selectedFolder != null && this.activeFilter.selectedFolderId != "none" && cipherPassesFilter ) { diff --git a/src/app/organizations/guards/permissions.guard.ts b/src/app/organizations/guards/permissions.guard.ts index 4ad57895..2f203559 100644 --- a/src/app/organizations/guards/permissions.guard.ts +++ b/src/app/organizations/guards/permissions.guard.ts @@ -1,5 +1,5 @@ import { Injectable } from "@angular/core"; -import { ActivatedRouteSnapshot, CanActivate, Router } from "@angular/router"; +import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from "@angular/router"; import { I18nService } from "jslib-common/abstractions/i18n.service"; import { OrganizationService } from "jslib-common/abstractions/organization.service"; @@ -17,7 +17,7 @@ export class PermissionsGuard implements CanActivate { private syncService: SyncService ) {} - async canActivate(route: ActivatedRouteSnapshot) { + async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { // TODO: We need to fix this issue once and for all. if ((await this.syncService.getLastSync()) == null) { await this.syncService.fullSync(false); @@ -39,6 +39,16 @@ export class PermissionsGuard implements CanActivate { const permissions = route.data == null ? [] : (route.data.permissions as Permissions[]); if (permissions != null && !org.hasAnyPermission(permissions)) { + // Handle linkable ciphers for organizations the user only has view access to + // https://bitwarden.atlassian.net/browse/EC-203 + if (state.root.queryParamMap.has("cipherId")) { + return this.router.createUrlTree(["/vault"], { + queryParams: { + cipherId: state.root.queryParamMap.get("cipherId"), + }, + }); + } + this.platformUtilsService.showToast("error", null, this.i18nService.t("accessDenied")); return this.router.createUrlTree(["/"]); } diff --git a/src/app/organizations/layouts/organization-layout.component.html b/src/app/organizations/layouts/organization-layout.component.html index cb0e6bba..3834635e 100644 --- a/src/app/organizations/layouts/organization-layout.component.html +++ b/src/app/organizations/layouts/organization-layout.component.html @@ -1,3 +1,4 @@ +
@@ -35,3 +36,4 @@
+ diff --git a/src/app/organizations/layouts/organization-switcher.component.html b/src/app/organizations/layouts/organization-switcher.component.html index 69bf5e59..7975fd9e 100644 --- a/src/app/organizations/layouts/organization-switcher.component.html +++ b/src/app/organizations/layouts/organization-switcher.component.html @@ -58,7 +58,7 @@
  • - + {{ "newOrganization" | i18n }} diff --git a/src/app/organizations/manage/collections.component.ts b/src/app/organizations/manage/collections.component.ts index ca7a9b07..f20a4cf5 100644 --- a/src/app/organizations/manage/collections.component.ts +++ b/src/app/organizations/manage/collections.component.ts @@ -20,8 +20,9 @@ import { import { ListResponse } from "jslib-common/models/response/listResponse"; import { CollectionView } from "jslib-common/models/view/collectionView"; +import { EntityUsersComponent } from "../../modules/organizations/manage/entity-users.component"; + import { CollectionAddEditComponent } from "./collection-add-edit.component"; -import { EntityUsersComponent } from "./entity-users.component"; @Component({ selector: "app-org-manage-collections", diff --git a/src/app/organizations/manage/groups.component.ts b/src/app/organizations/manage/groups.component.ts index a877540f..bc8bbd3c 100644 --- a/src/app/organizations/manage/groups.component.ts +++ b/src/app/organizations/manage/groups.component.ts @@ -12,7 +12,8 @@ import { SearchService } from "jslib-common/abstractions/search.service"; import { Utils } from "jslib-common/misc/utils"; import { GroupResponse } from "jslib-common/models/response/groupResponse"; -import { EntityUsersComponent } from "./entity-users.component"; +import { EntityUsersComponent } from "../../modules/organizations/manage/entity-users.component"; + import { GroupAddEditComponent } from "./group-add-edit.component"; @Component({ diff --git a/src/app/organizations/manage/user-add-edit.component.html b/src/app/organizations/manage/user-add-edit.component.html index 130f91ee..c9047b7e 100644 --- a/src/app/organizations/manage/user-add-edit.component.html +++ b/src/app/organizations/manage/user-add-edit.component.html @@ -52,7 +52,7 @@ target="_blank" rel="noopener" appA11yTitle="{{ 'learnMore' | i18n }}" - href="https://bitwarden.com/help/provider-users/" + href="https://bitwarden.com/help/user-types-access-control/" > diff --git a/src/app/organizations/tools/exposed-passwords-report.component.ts b/src/app/organizations/tools/exposed-passwords-report.component.ts index 1a860864..e3b8f552 100644 --- a/src/app/organizations/tools/exposed-passwords-report.component.ts +++ b/src/app/organizations/tools/exposed-passwords-report.component.ts @@ -14,7 +14,7 @@ import { CipherView } from "jslib-common/models/view/cipherView"; import { ExposedPasswordsReportComponent as BaseExposedPasswordsReportComponent } from "../../reports/exposed-passwords-report.component"; @Component({ - selector: "app-exposed-passwords-report", + selector: "app-org-exposed-passwords-report", templateUrl: "../../reports/exposed-passwords-report.component.html", }) export class ExposedPasswordsReportComponent extends BaseExposedPasswordsReportComponent { @@ -41,12 +41,10 @@ export class ExposedPasswordsReportComponent extends BaseExposedPasswordsReportC } ngOnInit() { - const dynamicSuper = Object.getPrototypeOf(this.constructor.prototype); this.route.parent.parent.params.subscribe(async (params) => { this.organization = await this.organizationService.get(params.organizationId); this.manageableCiphers = await this.cipherService.getAll(); - // TODO: We should do something about this, calling super in an async function is bad - dynamicSuper.ngOnInit(); + await this.checkAccess(); }); } diff --git a/src/app/oss-routing.module.ts b/src/app/oss-routing.module.ts index 5ba88fd8..1ad5a2fc 100644 --- a/src/app/oss-routing.module.ts +++ b/src/app/oss-routing.module.ts @@ -155,6 +155,11 @@ const routes: Routes = [ .IndividualVaultModule, }, { path: "sends", component: SendComponent, data: { title: "Send" } }, + { + path: "create-organization", + component: CreateOrganizationComponent, + data: { titleId: "newOrganization" }, + }, { path: "settings", component: SettingsComponent, @@ -181,11 +186,6 @@ const routes: Routes = [ loadChildren: async () => (await import("./settings/subscription-routing.module")).SubscriptionRoutingModule, }, - { - path: "create-organization", - component: CreateOrganizationComponent, - data: { titleId: "newOrganization" }, - }, { path: "emergency-access", children: [ @@ -229,13 +229,13 @@ const routes: Routes = [ (await import("./reports/reports-routing.module")).ReportsRoutingModule, }, { path: "setup/families-for-enterprise", component: FamiliesForEnterpriseSetupComponent }, - { - path: "organizations", - loadChildren: async () => - (await import("./organizations/organizations.module")).OrganizationsModule, - }, ], }, + { + path: "organizations", + loadChildren: async () => + (await import("./organizations/organizations.module")).OrganizationsModule, + }, ]; @NgModule({ diff --git a/src/app/oss.module.ts b/src/app/oss.module.ts index 74471abd..a725fdbf 100644 --- a/src/app/oss.module.ts +++ b/src/app/oss.module.ts @@ -1,12 +1,21 @@ import { NgModule } from "@angular/core"; import { LooseComponentsModule } from "./modules/loose-components.module"; +import { OrganizationManageModule } from "./modules/organizations/manage/organization-manage.module"; +import { OrganizationUserModule } from "./modules/organizations/users/organization-user.module"; import { PipesModule } from "./modules/pipes/pipes.module"; import { VaultFilterModule } from "./modules/vault-filter/vault-filter.module"; import { OrganizationBadgeModule } from "./modules/vault/modules/organization-badge/organization-badge.module"; @NgModule({ - imports: [LooseComponentsModule, VaultFilterModule, OrganizationBadgeModule, PipesModule], + imports: [ + LooseComponentsModule, + VaultFilterModule, + OrganizationBadgeModule, + PipesModule, + OrganizationManageModule, + OrganizationUserModule, + ], exports: [LooseComponentsModule, VaultFilterModule, OrganizationBadgeModule, PipesModule], bootstrap: [], }) diff --git a/src/app/send/access.component.html b/src/app/send/access.component.html index 19bc107b..b5818610 100644 --- a/src/app/send/access.component.html +++ b/src/app/send/access.component.html @@ -1,7 +1,7 @@
    -

    Bitwarden Send

    +

    Bitwarden Send

    {{ "sendCreatorIdentifier" | i18n: creatorIdentifier }}

    diff --git a/src/app/services/event.service.ts b/src/app/services/event.service.ts index a851866b..02f50cf7 100644 --- a/src/app/services/event.service.ts +++ b/src/app/services/event.service.ts @@ -307,6 +307,9 @@ export class EventService { case EventType.Organization_DisabledKeyConnector: msg = humanReadableMsg = this.i18nService.t("disabledKeyConnector"); break; + case EventType.Organization_SponsorshipsSynced: + msg = humanReadableMsg = this.i18nService.t("sponsorshipsSynced"); + break; // Policies case EventType.Policy_Updated: { msg = this.i18nService.t("modifiedPolicyId", this.formatPolicyId(ev)); diff --git a/src/app/settings/change-password.component.ts b/src/app/settings/change-password.component.ts index 577c66b0..37246a84 100644 --- a/src/app/settings/change-password.component.ts +++ b/src/app/settings/change-password.component.ts @@ -68,6 +68,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent { if (await this.keyConnectorService.getUsesKeyConnector()) { this.router.navigate(["/settings/security/two-factor"]); } + await super.ngOnInit(); } async rotateEncKeyClicked() { diff --git a/src/app/settings/create-organization.component.html b/src/app/settings/create-organization.component.html index 815442fe..f5b4219a 100644 --- a/src/app/settings/create-organization.component.html +++ b/src/app/settings/create-organization.component.html @@ -1,5 +1,11 @@ -
    diff --git a/src/app/tools/generator.component.ts b/src/app/tools/generator.component.ts index 5917c8ee..ad825ba5 100644 --- a/src/app/tools/generator.component.ts +++ b/src/app/tools/generator.component.ts @@ -40,11 +40,13 @@ export class GeneratorComponent extends BaseGeneratorComponent { route, window ); - // Cannot use Firefox Relay on the web vault yet due to CORS issues with Firefox Relay API - this.forwardOptions.splice( - this.forwardOptions.findIndex((o) => o.value === "firefoxrelay"), - 1 - ); + if (platformUtilsService.isSelfHost()) { + // Cannot use Firefox Relay on self hosted web vaults due to CORS issues with Firefox Relay API + this.forwardOptions.splice( + this.forwardOptions.findIndex((o) => o.value === "firefoxrelay"), + 1 + ); + } } async history() { diff --git a/src/app/vault/share.component.html b/src/app/vault/share.component.html index a5771d16..1f56c8c3 100644 --- a/src/app/vault/share.component.html +++ b/src/app/vault/share.component.html @@ -77,7 +77,7 @@ diff --git a/src/locales/en/messages.json b/src/locales/en/messages.json index bb558461..6cd0bbd0 100644 --- a/src/locales/en/messages.json +++ b/src/locales/en/messages.json @@ -4163,7 +4163,7 @@ "message": "Password reset success!" }, "resetPasswordEnrollmentWarning": { - "message": "Enrollment will allow organization administrators to change your master password. Are you sure you want to enroll?" + "message": "Enrollment will allow organization administrators to change your master password" }, "resetPasswordPolicy": { "message": "Master Password Reset" @@ -4669,13 +4669,13 @@ "message": "Email Sent" }, "revokeSponsorshipConfirmation": { - "message": "After removing this account, the Families organization owner will be responsible for this subscription and related invoices. Are you sure you want to continue?" + "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" }, "removeSponsorshipSuccess": { "message": "Sponsorship Removed" }, - "ssoKeyConnectorUnavailable": { - "message": "Unable to reach the Key Connector, try again later." + "ssoKeyConnectorError": { + "message": "Key Connector error: make sure Key Connector is available and working correctly." }, "keyConnectorUrl": { "message": "Key Connector URL" @@ -5005,7 +5005,7 @@ "message": "Service" }, "unknownCipher": { - "message": "Unknown Item, you may need to login with another account to access this item." + "message": "Unknown Item, you may need to request permission to access this item." }, "cannotSponsorSelf": { "message": "You cannot redeem for the active account. Enter a different email." @@ -5041,6 +5041,9 @@ "message": "Last Sync", "Description": "Used as a prefix to indicate the last time a sync occured. Example \"Last sync 1968-11-16 00:00:00\"" }, + "sponsorshipsSynced": { + "message": "Self-hosted sponsorships synced." + }, "billingManagedByProvider": { "message": "Managed by $PROVIDER$", "placeholders": { diff --git a/src/scss/vault-filters.scss b/src/scss/vault-filters.scss index 552b0f75..301497ea 100644 --- a/src/scss/vault-filters.scss +++ b/src/scss/vault-filters.scss @@ -14,14 +14,6 @@ font-size: $font-size-base; } - a.create-organization-link { - &:hover { - @include themify($themes) { - color: themed("iconHover") !important; - } - } - } - button { @extend .no-btn; } @@ -116,6 +108,7 @@ } text-decoration: none; } + max-width: 90%; } .edit-button { diff --git a/webpack.config.js b/webpack.config.js index fdebe061..4a81e4de 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -204,8 +204,60 @@ const devServer = return [ { key: "Content-Security-Policy", - value: - "default-src 'self'; script-src 'self' 'sha256-ryoU+5+IUZTuUyTElqkrQGBJXr1brEv6r2CA62WUw8w=' https://js.stripe.com https://js.braintreegateway.com https://www.paypalobjects.com; style-src 'self' https://assets.braintreegateway.com https://*.paypal.com 'sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' 'sha256-JVRXyYPueLWdwGwY9m/7u4QlZ1xeQdqUj2t8OVIzZE4='; img-src 'self' data: https://icons.bitwarden.net https://*.paypal.com https://www.paypalobjects.com https://q.stripe.com https://haveibeenpwned.com https://www.gravatar.com; child-src 'self' https://js.stripe.com https://assets.braintreegateway.com https://*.paypal.com https://*.duosecurity.com; frame-src 'self' https://js.stripe.com https://assets.braintreegateway.com https://*.paypal.com https://*.duosecurity.com; connect-src 'self' wss://notifications.bitwarden.com https://notifications.bitwarden.com https://cdn.bitwarden.net https://api.pwnedpasswords.com https://2fa.directory/api/v3/totp.json https://api.stripe.com https://www.paypal.com https://api.braintreegateway.com https://client-analytics.braintreegateway.com https://*.braintree-api.com https://*.blob.core.windows.net https://app.simplelogin.io/api/alias/random/new https://app.anonaddy.com/api/v1/aliases; object-src 'self' blob:;", + value: ` + default-src 'self'; + script-src + 'self' + 'sha256-ryoU+5+IUZTuUyTElqkrQGBJXr1brEv6r2CA62WUw8w=' + https://js.stripe.com + https://js.braintreegateway.com + https://www.paypalobjects.com; + style-src + 'self' + https://assets.braintreegateway.com + https://*.paypal.com + 'sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' + 'sha256-JVRXyYPueLWdwGwY9m/7u4QlZ1xeQdqUj2t8OVIzZE4='; + 'sha256-0xHKHIT3+e2Gknxsm/cpErSprhL+o254L/y5bljg74U=' + img-src + 'self' + data: + https://icons.bitwarden.net + https://*.paypal.com + https://www.paypalobjects.com + https://q.stripe.com + https://haveibeenpwned.com + https://www.gravatar.com; + child-src + 'self' + https://js.stripe.com + https://assets.braintreegateway.com + https://*.paypal.com + https://*.duosecurity.com; + frame-src + 'self' + https://js.stripe.com + https://assets.braintreegateway.com + https://*.paypal.com + https://*.duosecurity.com; + connect-src + 'self' + wss://notifications.bitwarden.com + https://notifications.bitwarden.com + https://cdn.bitwarden.net + https://api.pwnedpasswords.com + https://2fa.directory/api/v3/totp.json + https://api.stripe.com + https://www.paypal.com + https://api.braintreegateway.com + https://client-analytics.braintreegateway.com + https://*.braintree-api.com + https://*.blob.core.windows.net + https://app.simplelogin.io/api/alias/random/new + https://app.anonaddy.com/api/v1/aliases; + object-src + 'self' + blob:;`, }, ]; }