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 @@ -67,20 +67,11 @@ class ServletAwareConfigurator extends ServerEndpointConfig.Configurator with La
s"User ID: $userId, User Name: $userName, User Email: $userEmail with CU Access: $cuAccess"
)

config.getUserProperties.put(
classOf[User].getName,
new User(
userId,
userName,
userEmail,
null,
null,
null,
null,
null,
null
)
)
val user = new User()
user.setUid(userId)
user.setName(userName)
user.setEmail(userEmail)
config.getUserProperties.put(classOf[User].getName, user)
logger.debug(s"User created from headers: ID=$userId, Name=$userName")
} else {
// SINGLE NODE MODE: Construct the User object from JWT in query parameters.
Expand All @@ -96,20 +87,11 @@ class ServletAwareConfigurator extends ServerEndpointConfig.Configurator with La
.get("access-token")
.map(token => {
val claims = jwtConsumer.process(token).getJwtClaims
config.getUserProperties.put(
classOf[User].getName,
new User(
claims.getClaimValue("userId").asInstanceOf[Long].toInt,
claims.getSubject,
String.valueOf(claims.getClaimValue("email").asInstanceOf[String]),
null,
null,
null,
null,
null,
null
)
)
val user = new User()
user.setUid(claims.getClaimValue("userId").asInstanceOf[Long].toInt)
user.setName(claims.getSubject)
user.setEmail(String.valueOf(claims.getClaimValue("email").asInstanceOf[String]))
config.getUserProperties.put(classOf[User].getName, user)
})
}
} catch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,12 @@ import javax.ws.rs.core.SecurityContext
override protected def newInstance = new GuestAuthFilter
}

val GUEST: User =
new User(null, "guest", null, null, null, null, UserRoleEnum.REGULAR, null, null)
val GUEST: User = {
val user = new User()
user.setName("guest")
user.setRole(UserRoleEnum.REGULAR)
user
}
}

@PreMatching
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,16 @@ object UserAuthenticator extends Authenticator[JwtContext, SessionUser] with Laz
val comment = context.getJwtClaims.getClaimValue("comment").asInstanceOf[String]
val accountCreation =
context.getJwtClaims.getClaimValue("accountCreation").asInstanceOf[OffsetDateTime]
val user =
new User(userId, userName, email, null, googleId, null, role, comment, accountCreation)

val user = new User()
user.setUid(userId)
user.setName(userName)
user.setEmail(email)
user.setGoogleId(googleId)
user.setRole(role)
user.setComment(comment)
user.setAccountCreationTime(accountCreation)

Optional.of(new SessionUser(user))
} catch {
case e: Exception =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,40 @@ case class UserInfo(
googleAvatar: String,
comment: String,
lastLogin: java.time.OffsetDateTime, // will be null if never logged in
accountCreation: java.time.OffsetDateTime
accountCreation: java.time.OffsetDateTime,
permission: String // JSON string representing user permissions
)

// Permission field schema definition
case class PermissionFieldSchema(
fieldType: String, // "boolean", "number", or "string"
possibleValues: List[Any], // List of possible values, empty list if not a category field
defaultValue: Any, // Default value for this permission
description: String // Human-readable description of what this permission does
)

// Permission template containing all available permissions
case class PermissionTemplate(
permissions: Map[String, PermissionFieldSchema]
)

object AdminUserResource {
final private lazy val context = SqlServer
.getInstance()
.createDSLContext()
final private lazy val userDao = new UserDao(context.configuration)

// Define the permission template with all available permissions
val permissionTemplate: PermissionTemplate = PermissionTemplate(
permissions = Map(
"sshToComputingUnit" -> PermissionFieldSchema(
fieldType = "boolean",
possibleValues = List(true, false),
defaultValue = false,
description = "Allow user to access SSH terminal for computing units they have access to"
)
)
)
}

@Path("/admin/user")
Expand All @@ -78,7 +104,8 @@ class AdminUserResource {
USER.GOOGLE_AVATAR,
USER.COMMENT,
USER_LAST_ACTIVE_TIME.LAST_ACTIVE_TIME,
USER.ACCOUNT_CREATION_TIME
USER.ACCOUNT_CREATION_TIME,
USER.PERMISSION
)
.from(USER)
.leftJoin(USER_LAST_ACTIVE_TIME)
Expand All @@ -99,6 +126,7 @@ class AdminUserResource {
updatedUser.setEmail(user.getEmail)
updatedUser.setRole(user.getRole)
updatedUser.setComment(user.getComment)
updatedUser.setPermission(user.getPermission)
userDao.update(updatedUser)

if (roleChanged)
Expand Down Expand Up @@ -145,4 +173,17 @@ class AdminUserResource {
def deleteCollection(@PathParam("eid") eid: Integer): Unit = {
deleteExecutionCollection(eid)
}

/**
* Returns the permission template that describes all available user permissions
* including their types, possible values, and default values.
*
* @return PermissionTemplate containing the schema for all permissions
*/
@GET
@Path("/permission")
@Produces(Array(MediaType.APPLICATION_JSON))
def getPermissionTemplate(): PermissionTemplate = {
AdminUserResource.permissionTemplate
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,12 @@ object JwtParser extends LazyLogging {
val role = UserRoleEnum.valueOf(jwtClaims.getClaimValue("role").asInstanceOf[String])
val googleId = jwtClaims.getClaimValue("googleId", classOf[String])

val user = new User(userId, userName, email, null, googleId, null, role, null, null)
val user = new User()
user.setUid(userId)
user.setName(userName)
user.setEmail(email)
user.setGoogleId(googleId)
user.setRole(role)
Optional.of(new SessionUser(user))
} catch {
case _: UnresolvableKeyException =>
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ import { AdminSettingsComponent } from "./dashboard/component/admin/settings/adm
import { FormlyRepeatDndComponent } from "./common/formly/repeat-dnd/repeat-dnd.component";
import { NzInputNumberModule } from "ng-zorro-antd/input-number";
import { NzCheckboxModule } from "ng-zorro-antd/checkbox";
import { PermissionEditModalComponent } from "./dashboard/component/admin/user/permission-edit-modal/permission-edit-modal.component";

registerLocaleData(en);

Expand Down Expand Up @@ -268,6 +269,7 @@ registerLocaleData(en);
HubSearchResultComponent,
ComputingUnitSelectionComponent,
AdminSettingsComponent,
PermissionEditModalComponent,
],
imports: [
BrowserModule,
Expand Down
1 change: 1 addition & 0 deletions frontend/src/app/common/type/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export interface User
comment: string;
lastLogin?: number;
accountCreation?: Second;
permission?: string; // JSON string representing user permissions
}> {}

export interface File
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
User Role
</th>
<th>Quota</th>
<th>Permission</th>
<th>Account Creation Time</th>
</tr>
</thead>
Expand Down Expand Up @@ -294,6 +295,18 @@
nzType="dashboard"></i>
</button>
</td>
<td>
<button
(click)="openPermissionModal(user)"
nz-button
nz-tooltip="View and edit permissions"
type="button">
<i
nz-icon
nzTheme="outline"
nzType="setting"></i>
</button>
</td>
<td>
<ng-container *ngIf="getAccountCreation(user) as ac; else noAC">
{{ ac | date:'MM/dd/y, h:mm a' }}
Expand All @@ -310,3 +323,11 @@
Add
</button>
</nz-table>

<texera-permission-edit-modal
[isVisible]="isPermissionModalVisible"
[permissionTemplate]="permissionTemplate"
[user]="selectedUserForPermission"
(save)="handlePermissionSave($event)"
(cancel)="handlePermissionCancel()">
</texera-permission-edit-modal>
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { UntilDestroy, untilDestroyed } from "@ngneat/until-destroy";
import { NzTableFilterFn, NzTableSortFn } from "ng-zorro-antd/table";
import { NzModalService } from "ng-zorro-antd/modal";
import { NzMessageService } from "ng-zorro-antd/message";
import { AdminUserService } from "../../../service/admin/user/admin-user.service";
import { AdminUserService, PermissionTemplate } from "../../../service/admin/user/admin-user.service";
import { MilliSecond, Role, User } from "../../../../common/type/user";
import { UserService } from "../../../../common/service/user/user.service";
import { UserQuotaComponent } from "../../user/user-quota/user-quota.component";
Expand All @@ -41,6 +41,7 @@ export class AdminUserComponent implements OnInit {
editEmail: string = "";
editRole: Role = Role.REGULAR;
editComment: string = "";
editPermission: string = "{}";
nameSearchValue: string = "";
emailSearchValue: string = "";
commentSearchValue: string = "";
Expand All @@ -49,6 +50,9 @@ export class AdminUserComponent implements OnInit {
commentSearchVisible = false;
listOfDisplayUser = [...this.userList];
currentUid: number | undefined = 0;
permissionTemplate: PermissionTemplate | null = null;
isPermissionModalVisible: boolean = false;
selectedUserForPermission: User | null = null;

@ViewChild("nameInput") nameInputRef?: ElementRef<HTMLInputElement>;
@ViewChild("emailInput") emailInputRef?: ElementRef<HTMLInputElement>;
Expand All @@ -72,6 +76,13 @@ export class AdminUserComponent implements OnInit {
this.userList = userList;
this.reset();
});

this.adminUserService
.getPermissionTemplate()
.pipe(untilDestroyed(this))
.subscribe(template => {
this.permissionTemplate = template;
});
}

public updateRole(user: User, role: Role): void {
Expand All @@ -94,6 +105,7 @@ export class AdminUserComponent implements OnInit {
this.editEmail = user.email;
this.editRole = user.role;
this.editComment = user.comment;
this.editPermission = user.permission || "{}";

setTimeout(() => {
if (attribute === "name" && this.nameInputRef) {
Expand All @@ -119,7 +131,8 @@ export class AdminUserComponent implements OnInit {
(originalUser.name === this.editName &&
originalUser.email === this.editEmail &&
originalUser.comment === this.editComment &&
originalUser.role === this.editRole)
originalUser.role === this.editRole &&
(originalUser.permission || "{}") === this.editPermission)
) {
this.stopEdit();
return;
Expand All @@ -128,7 +141,7 @@ export class AdminUserComponent implements OnInit {
const currentUid = this.editUid;
this.stopEdit();
this.adminUserService
.updateUser(currentUid, this.editName, this.editEmail, this.editRole, this.editComment)
.updateUser(currentUid, this.editName, this.editEmail, this.editRole, this.editComment, this.editPermission)
.pipe(untilDestroyed(this))
.subscribe({
next: () => this.ngOnInit(),
Expand Down Expand Up @@ -186,6 +199,25 @@ export class AdminUserComponent implements OnInit {
});
}

openPermissionModal(user: User): void {
this.selectedUserForPermission = user;
this.startEdit(user, "permission");
this.isPermissionModalVisible = true;
}

handlePermissionSave(editedPermission: string): void {
this.editPermission = editedPermission;
this.saveEdit();
this.isPermissionModalVisible = false;
this.selectedUserForPermission = null;
}

handlePermissionCancel(): void {
this.stopEdit();
this.isPermissionModalVisible = false;
this.selectedUserForPermission = null;
}

isUserActive(user: User): boolean {
if (!user.lastLogin) {
return false;
Expand Down
Loading
Loading