Skip to content

Commit fc917b3

Browse files
committed
finish prototype
1 parent bfca002 commit fc917b3

File tree

14 files changed

+405
-39
lines changed

14 files changed

+405
-39
lines changed

amber/src/main/scala/org/apache/texera/web/ServletAwareConfigurator.scala

Lines changed: 10 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -67,20 +67,11 @@ class ServletAwareConfigurator extends ServerEndpointConfig.Configurator with La
6767
s"User ID: $userId, User Name: $userName, User Email: $userEmail with CU Access: $cuAccess"
6868
)
6969

70-
config.getUserProperties.put(
71-
classOf[User].getName,
72-
new User(
73-
userId,
74-
userName,
75-
userEmail,
76-
null,
77-
null,
78-
null,
79-
null,
80-
null,
81-
null
82-
)
83-
)
70+
val user = new User()
71+
user.setUid(userId)
72+
user.setName(userName)
73+
user.setEmail(userEmail)
74+
config.getUserProperties.put(classOf[User].getName, user)
8475
logger.debug(s"User created from headers: ID=$userId, Name=$userName")
8576
} else {
8677
// SINGLE NODE MODE: Construct the User object from JWT in query parameters.
@@ -96,20 +87,11 @@ class ServletAwareConfigurator extends ServerEndpointConfig.Configurator with La
9687
.get("access-token")
9788
.map(token => {
9889
val claims = jwtConsumer.process(token).getJwtClaims
99-
config.getUserProperties.put(
100-
classOf[User].getName,
101-
new User(
102-
claims.getClaimValue("userId").asInstanceOf[Long].toInt,
103-
claims.getSubject,
104-
String.valueOf(claims.getClaimValue("email").asInstanceOf[String]),
105-
null,
106-
null,
107-
null,
108-
null,
109-
null,
110-
null
111-
)
112-
)
90+
val user = new User()
91+
user.setUid(claims.getClaimValue("userId").asInstanceOf[Long].toInt)
92+
user.setName(claims.getSubject)
93+
user.setEmail(String.valueOf(claims.getClaimValue("email").asInstanceOf[String]))
94+
config.getUserProperties.put(classOf[User].getName, user)
11395
})
11496
}
11597
} catch {

amber/src/main/scala/org/apache/texera/web/auth/GuestAuthFilter.scala

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,12 @@ import javax.ws.rs.core.SecurityContext
3838
override protected def newInstance = new GuestAuthFilter
3939
}
4040

41-
val GUEST: User =
42-
new User(null, "guest", null, null, null, null, UserRoleEnum.REGULAR, null, null)
41+
val GUEST: User = {
42+
val user = new User()
43+
user.setName("guest")
44+
user.setRole(UserRoleEnum.REGULAR)
45+
user
46+
}
4347
}
4448

4549
@PreMatching

amber/src/main/scala/org/apache/texera/web/auth/UserAuthenticator.scala

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,16 @@ object UserAuthenticator extends Authenticator[JwtContext, SessionUser] with Laz
4343
val comment = context.getJwtClaims.getClaimValue("comment").asInstanceOf[String]
4444
val accountCreation =
4545
context.getJwtClaims.getClaimValue("accountCreation").asInstanceOf[OffsetDateTime]
46-
val user =
47-
new User(userId, userName, email, null, googleId, null, role, comment, accountCreation)
46+
47+
val user = new User()
48+
user.setUid(userId)
49+
user.setName(userName)
50+
user.setEmail(email)
51+
user.setGoogleId(googleId)
52+
user.setRole(role)
53+
user.setComment(comment)
54+
user.setAccountCreationTime(accountCreation)
55+
4856
Optional.of(new SessionUser(user))
4957
} catch {
5058
case e: Exception =>

amber/src/main/scala/org/apache/texera/web/resource/dashboard/admin/user/AdminUserResource.scala

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,40 @@ case class UserInfo(
4545
googleAvatar: String,
4646
comment: String,
4747
lastLogin: java.time.OffsetDateTime, // will be null if never logged in
48-
accountCreation: java.time.OffsetDateTime
48+
accountCreation: java.time.OffsetDateTime,
49+
permission: String // JSON string representing user permissions
50+
)
51+
52+
// Permission field schema definition
53+
case class PermissionFieldSchema(
54+
fieldType: String, // "boolean", "number", or "string"
55+
possibleValues: List[Any], // List of possible values, empty list if not a category field
56+
defaultValue: Any, // Default value for this permission
57+
description: String // Human-readable description of what this permission does
58+
)
59+
60+
// Permission template containing all available permissions
61+
case class PermissionTemplate(
62+
permissions: Map[String, PermissionFieldSchema]
4963
)
5064

5165
object AdminUserResource {
5266
final private lazy val context = SqlServer
5367
.getInstance()
5468
.createDSLContext()
5569
final private lazy val userDao = new UserDao(context.configuration)
70+
71+
// Define the permission template with all available permissions
72+
val permissionTemplate: PermissionTemplate = PermissionTemplate(
73+
permissions = Map(
74+
"sshToComputingUnit" -> PermissionFieldSchema(
75+
fieldType = "boolean",
76+
possibleValues = List(true, false),
77+
defaultValue = false,
78+
description = "Allow user to access SSH terminal for computing units they have access to"
79+
)
80+
)
81+
)
5682
}
5783

5884
@Path("/admin/user")
@@ -78,7 +104,8 @@ class AdminUserResource {
78104
USER.GOOGLE_AVATAR,
79105
USER.COMMENT,
80106
USER_LAST_ACTIVE_TIME.LAST_ACTIVE_TIME,
81-
USER.ACCOUNT_CREATION_TIME
107+
USER.ACCOUNT_CREATION_TIME,
108+
USER.PERMISSION
82109
)
83110
.from(USER)
84111
.leftJoin(USER_LAST_ACTIVE_TIME)
@@ -99,6 +126,7 @@ class AdminUserResource {
99126
updatedUser.setEmail(user.getEmail)
100127
updatedUser.setRole(user.getRole)
101128
updatedUser.setComment(user.getComment)
129+
updatedUser.setPermission(user.getPermission)
102130
userDao.update(updatedUser)
103131

104132
if (roleChanged)
@@ -145,4 +173,17 @@ class AdminUserResource {
145173
def deleteCollection(@PathParam("eid") eid: Integer): Unit = {
146174
deleteExecutionCollection(eid)
147175
}
176+
177+
/**
178+
* Returns the permission template that describes all available user permissions
179+
* including their types, possible values, and default values.
180+
*
181+
* @return PermissionTemplate containing the schema for all permissions
182+
*/
183+
@GET
184+
@Path("/permission")
185+
@Produces(Array(MediaType.APPLICATION_JSON))
186+
def getPermissionTemplate(): PermissionTemplate = {
187+
AdminUserResource.permissionTemplate
188+
}
148189
}

common/auth/src/main/scala/org/apache/texera/auth/JwtParser.scala

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,12 @@ object JwtParser extends LazyLogging {
5252
val role = UserRoleEnum.valueOf(jwtClaims.getClaimValue("role").asInstanceOf[String])
5353
val googleId = jwtClaims.getClaimValue("googleId", classOf[String])
5454

55-
val user = new User(userId, userName, email, null, googleId, null, role, null, null)
55+
val user = new User()
56+
user.setUid(userId)
57+
user.setName(userName)
58+
user.setEmail(email)
59+
user.setGoogleId(googleId)
60+
user.setRole(role)
5661
Optional.of(new SessionUser(user))
5762
} catch {
5863
case _: UnresolvableKeyException =>

frontend/src/app/app.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ import { AdminSettingsComponent } from "./dashboard/component/admin/settings/adm
176176
import { FormlyRepeatDndComponent } from "./common/formly/repeat-dnd/repeat-dnd.component";
177177
import { NzInputNumberModule } from "ng-zorro-antd/input-number";
178178
import { NzCheckboxModule } from "ng-zorro-antd/checkbox";
179+
import { PermissionEditModalComponent } from "./dashboard/component/admin/user/permission-edit-modal/permission-edit-modal.component";
179180

180181
registerLocaleData(en);
181182

@@ -268,6 +269,7 @@ registerLocaleData(en);
268269
HubSearchResultComponent,
269270
ComputingUnitSelectionComponent,
270271
AdminSettingsComponent,
272+
PermissionEditModalComponent,
271273
],
272274
imports: [
273275
BrowserModule,

frontend/src/app/common/type/user.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export interface User
4646
comment: string;
4747
lastLogin?: number;
4848
accountCreation?: Second;
49+
permission?: string; // JSON string representing user permissions
4950
}> {}
5051

5152
export interface File

frontend/src/app/dashboard/component/admin/user/admin-user.component.html

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
User Role
9393
</th>
9494
<th>Quota</th>
95+
<th>Permission</th>
9596
<th>Account Creation Time</th>
9697
</tr>
9798
</thead>
@@ -294,6 +295,18 @@
294295
nzType="dashboard"></i>
295296
</button>
296297
</td>
298+
<td>
299+
<button
300+
(click)="openPermissionModal(user)"
301+
nz-button
302+
nzSize="small"
303+
nz-tooltip="View and edit permissions">
304+
<i
305+
nz-icon
306+
nzType="setting"></i>
307+
Permissions
308+
</button>
309+
</td>
297310
<td>
298311
<ng-container *ngIf="getAccountCreation(user) as ac; else noAC">
299312
{{ ac | date:'MM/dd/y, h:mm a' }}
@@ -310,3 +323,11 @@
310323
Add
311324
</button>
312325
</nz-table>
326+
327+
<texera-permission-edit-modal
328+
[isVisible]="isPermissionModalVisible"
329+
[permissionTemplate]="permissionTemplate"
330+
[user]="selectedUserForPermission"
331+
(save)="handlePermissionSave($event)"
332+
(cancel)="handlePermissionCancel()">
333+
</texera-permission-edit-modal>

frontend/src/app/dashboard/component/admin/user/admin-user.component.ts

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { UntilDestroy, untilDestroyed } from "@ngneat/until-destroy";
2222
import { NzTableFilterFn, NzTableSortFn } from "ng-zorro-antd/table";
2323
import { NzModalService } from "ng-zorro-antd/modal";
2424
import { NzMessageService } from "ng-zorro-antd/message";
25-
import { AdminUserService } from "../../../service/admin/user/admin-user.service";
25+
import { AdminUserService, PermissionTemplate } from "../../../service/admin/user/admin-user.service";
2626
import { MilliSecond, Role, User } from "../../../../common/type/user";
2727
import { UserService } from "../../../../common/service/user/user.service";
2828
import { UserQuotaComponent } from "../../user/user-quota/user-quota.component";
@@ -41,6 +41,7 @@ export class AdminUserComponent implements OnInit {
4141
editEmail: string = "";
4242
editRole: Role = Role.REGULAR;
4343
editComment: string = "";
44+
editPermission: string = "{}";
4445
nameSearchValue: string = "";
4546
emailSearchValue: string = "";
4647
commentSearchValue: string = "";
@@ -49,6 +50,9 @@ export class AdminUserComponent implements OnInit {
4950
commentSearchVisible = false;
5051
listOfDisplayUser = [...this.userList];
5152
currentUid: number | undefined = 0;
53+
permissionTemplate: PermissionTemplate | null = null;
54+
isPermissionModalVisible: boolean = false;
55+
selectedUserForPermission: User | null = null;
5256

5357
@ViewChild("nameInput") nameInputRef?: ElementRef<HTMLInputElement>;
5458
@ViewChild("emailInput") emailInputRef?: ElementRef<HTMLInputElement>;
@@ -72,6 +76,13 @@ export class AdminUserComponent implements OnInit {
7276
this.userList = userList;
7377
this.reset();
7478
});
79+
80+
this.adminUserService
81+
.getPermissionTemplate()
82+
.pipe(untilDestroyed(this))
83+
.subscribe(template => {
84+
this.permissionTemplate = template;
85+
});
7586
}
7687

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

98110
setTimeout(() => {
99111
if (attribute === "name" && this.nameInputRef) {
@@ -119,7 +131,8 @@ export class AdminUserComponent implements OnInit {
119131
(originalUser.name === this.editName &&
120132
originalUser.email === this.editEmail &&
121133
originalUser.comment === this.editComment &&
122-
originalUser.role === this.editRole)
134+
originalUser.role === this.editRole &&
135+
(originalUser.permission || "{}") === this.editPermission)
123136
) {
124137
this.stopEdit();
125138
return;
@@ -128,7 +141,7 @@ export class AdminUserComponent implements OnInit {
128141
const currentUid = this.editUid;
129142
this.stopEdit();
130143
this.adminUserService
131-
.updateUser(currentUid, this.editName, this.editEmail, this.editRole, this.editComment)
144+
.updateUser(currentUid, this.editName, this.editEmail, this.editRole, this.editComment, this.editPermission)
132145
.pipe(untilDestroyed(this))
133146
.subscribe({
134147
next: () => this.ngOnInit(),
@@ -186,6 +199,25 @@ export class AdminUserComponent implements OnInit {
186199
});
187200
}
188201

202+
openPermissionModal(user: User): void {
203+
this.selectedUserForPermission = user;
204+
this.startEdit(user, "permission");
205+
this.isPermissionModalVisible = true;
206+
}
207+
208+
handlePermissionSave(editedPermission: string): void {
209+
this.editPermission = editedPermission;
210+
this.saveEdit();
211+
this.isPermissionModalVisible = false;
212+
this.selectedUserForPermission = null;
213+
}
214+
215+
handlePermissionCancel(): void {
216+
this.stopEdit();
217+
this.isPermissionModalVisible = false;
218+
this.selectedUserForPermission = null;
219+
}
220+
189221
isUserActive(user: User): boolean {
190222
if (!user.lastLogin) {
191223
return false;

0 commit comments

Comments
 (0)