Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import org.apache.texera.dao.jooq.generated.tables.pojos.WorkflowUserAccess
import org.apache.texera.web.model.common.AccessEntry
import org.apache.texera.web.resource.dashboard.user.workflow.WorkflowAccessResource.{
context,
getPrivilege,
hasWriteAccess
}
import org.jooq.DSLContext
Expand Down Expand Up @@ -174,10 +175,16 @@ class WorkflowAccessResource() {
@PathParam("privilege") privilege: String,
@Auth user: SessionUser
): Unit = {
if (email.equals(user.getEmail)) {
val isModifyingOwnAccess = email.equals(user.getEmail)
val currentPrivilege = getPrivilege(wid, user.getUid)
val hasExistingAccess = !currentPrivilege.eq(PrivilegeEnum.NONE)

// Users can only modify their own access if they already have access
if (isModifyingOwnAccess && !hasExistingAccess) {
throw new BadRequestException("You cannot grant access to yourself!")
}

// Must have write access to modify access levels (including your own)
if (!hasWriteAccess(wid, user.getUid)) {
throw new ForbiddenException(s"You do not have permission to modify workflow $wid")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,16 @@
id="current-share">
<li><nz-tag nzColor="green">OWNER</nz-tag> {{ owner }}</li>
<li *ngFor="let entry of accessList">
<nz-tag nzColor="blue">{{ entry.privilege }}</nz-tag> {{ entry.email }} ({{ entry.name }})
<select
[value]="entry.privilege"
[disabled]="!hasWriteAccess"
(change)="changeAccessLevel(entry.email, $any($event.target).value)">
<option value="READ">READ</option>
<option value="WRITE">WRITE</option>
</select>
{{ entry.email }} ({{ entry.name }})
<button
[disabled]="!writeAccess && entry.email !== currentEmail"
[disabled]="!hasWriteAccess && entry.email !== currentEmail"
(click)="verifyRevokeAccess(entry.email)"
nz-button
nz-tooltip="revoke access"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import { Component, EventEmitter, inject, OnDestroy, OnInit, Output } from "@angular/core";
import { FormBuilder, FormControl, FormGroup, Validators } from "@angular/forms";
import { ShareAccessService } from "../../../service/user/share-access/share-access.service";
import { ShareAccess } from "../../../type/share-access.interface";
import { Privilege, ShareAccess } from "../../../type/share-access.interface";
import { UntilDestroy, untilDestroyed } from "@ngneat/until-destroy";
import { UserService } from "../../../../common/service/user/user.service";
import { GmailService } from "../../../../common/service/gmail/gmail.service";
Expand Down Expand Up @@ -76,6 +76,17 @@ export class ShareAccessComponent implements OnInit, OnDestroy {
this.currentEmail = this.userService.getCurrentUser()?.email;
}

get hasWriteAccess(): boolean {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is an existing writeAccess variable that is still used by other components. All other usages of the old variable should be replaced with hasWriteAccess to avoid stale states in other places.

if (!this.currentEmail) {
return false;
}
if (this.currentEmail === this.owner) {
return true;
}
const currentUserAccess = this.accessList.find(entry => entry.email === this.currentEmail);
return currentUserAccess?.privilege === Privilege.WRITE;
}

ngOnInit(): void {
this.accessService
.getAccessList(this.type, this.id)
Expand Down Expand Up @@ -238,6 +249,57 @@ export class ShareAccessComponent implements OnInit, OnDestroy {
});
}

public changeAccessLevel(email: string, newPrivilege: string): void {
const isOwnAccess = email === this.currentEmail;
const currentUserAccess = this.accessList.find(entry => entry.email === email);
const isDowngrade = currentUserAccess?.privilege === Privilege.WRITE && newPrivilege === "READ";

if (isOwnAccess && isDowngrade) {
const modal: NzModalRef = this.modalService.create({
nzTitle: "Downgrade Your Access",
nzContent: `Are you sure you want to change your own access to READ? You will no longer be able to edit this ${this.type} or manage access.`,
nzFooter: [
{
label: "Cancel",
onClick: () => {
modal.close();
this.ngOnInit();
},
},
{
label: "Confirm",
type: "primary",
danger: true,
onClick: () => {
this.doChangeAccessLevel(email, newPrivilege);
modal.close();
},
},
],
});
} else {
this.doChangeAccessLevel(email, newPrivilege);
}
}

private doChangeAccessLevel(email: string, newPrivilege: string): void {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name is awkward. Consider something like applyAccessLevelChange.

this.accessService
.grantAccess(this.type, this.id, email, newPrivilege)
.pipe(untilDestroyed(this))
.subscribe({
next: () => {
this.notificationService.success(`Access level for ${email} changed to ${newPrivilege}.`);
this.ngOnInit();
},
error: (error: unknown) => {
if (error instanceof HttpErrorResponse) {
this.notificationService.error(error.error.message);
}
this.ngOnInit();
},
});
}

public verifyPublish(): void {
if (!this.isPublic) {
const modal: NzModalRef = this.modalService.create({
Expand Down
Loading