Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
92 commits
Select commit Hold shift + click to select a range
67d7529
feat: permission-service
aicam Jul 17, 2025
f82f6a4
fix: headers and config
aicam Jul 28, 2025
0f7e184
Merge branch 'master' into permission-service
aicam Jul 28, 2025
83745c3
fix: service build
aicam Jul 29, 2025
d6cb177
fix: arrow failed test case
aicam Jul 29, 2025
0e581e7
fix: downgrading arrow lib in R
aicam Jul 29, 2025
c0102f4
fix: downgrading arrow lib in R
aicam Jul 29, 2025
12cdf7d
fix: downgrading arrow lib in R
aicam Jul 29, 2025
4d6af65
fix: downgrading arrow lib in R
aicam Jul 29, 2025
fd6c9ad
fix: downgrading arrow lib in R
aicam Jul 29, 2025
82be22a
fix: downgrading arrow lib in R
aicam Jul 29, 2025
10c05f6
fix: downgrading arrow lib in R
aicam Jul 29, 2025
c97227b
fix: downgrading arrow lib in R
aicam Jul 29, 2025
27d4afb
fix: downgrading arrow lib in R
aicam Jul 29, 2025
e76a6e7
fix: downgrading arrow lib in R
aicam Jul 29, 2025
aec7aee
fix: downgrading arrow lib in R
aicam Jul 29, 2025
c65a1db
fix: downgrading arrow lib in R
aicam Jul 29, 2025
377863f
fix: downgrading arrow lib in R
aicam Jul 29, 2025
5cb1037
fix: downgrading arrow lib in R
aicam Jul 29, 2025
9da3556
fix: downgrading arrow lib in R
aicam Jul 30, 2025
65b065c
fix: downgrading arrow lib in R
aicam Jul 30, 2025
38d9bf7
Merge branch 'Texera:master' into master
aicam Aug 5, 2025
d054a39
Merge branch 'refs/heads/master' into permission-service
aicam Aug 5, 2025
ed19464
Merge branch 'apache:master' into master
aicam Aug 8, 2025
a7cfdc1
fix: access usage
aicam Aug 8, 2025
4dd56ad
Merge branch 'master' into permission-service
aicam Aug 8, 2025
a6a4ec4
fix: rename and front
aicam Aug 11, 2025
2043ec9
Merge branch 'apache:master' into master
aicam Aug 11, 2025
51fac50
Merge branch 'apache:master' into master
aicam Aug 11, 2025
946c6ff
Merge branch 'apache:master' into master
aicam Aug 14, 2025
98120f9
Merge branch 'apache:master' into master
aicam Aug 18, 2025
1b1cd29
Merge branch 'apache:master' into master
aicam Aug 18, 2025
daf9e7a
Merge branch 'master' into permission-service
aicam Aug 18, 2025
75be5cd
Merge branch 'master' into permission-service
aicam Aug 18, 2025
a10ea90
fix: comments
aicam Aug 18, 2025
9ea6401
fix: comments
aicam Aug 18, 2025
33f219e
Merge branch 'master' into permission-service
aicam Aug 18, 2025
38f98a4
fix: Apache headers
aicam Aug 18, 2025
5a23684
fix: Apache headers
aicam Aug 18, 2025
1efb235
fix: naming
aicam Aug 19, 2025
ca9a4be
Merge branch 'apache:main' into master
aicam Aug 20, 2025
8fc9be1
Merge branch 'main' into permission-service
aicam Aug 20, 2025
9de6e93
fix: check header
aicam Aug 20, 2025
f0dd32f
Merge branch 'main' into permission-service
aicam Aug 25, 2025
57fbcc5
fix: comments
aicam Aug 26, 2025
0facebf
Merge branch 'main' into permission-service
aicam Aug 26, 2025
055f671
Merge remote-tracking branch 'origin/permission-service' into permiss…
aicam Aug 26, 2025
16f33e1
fix: Apache header
aicam Aug 26, 2025
9f1f2fb
Merge branch 'main' into permission-service
aicam Aug 26, 2025
879659b
Merge branch 'apache:main' into main
aicam Aug 27, 2025
feb2c4d
Merge branch 'apache:main' into main
aicam Aug 29, 2025
774f017
Merge branch 'main' into permission-service
aicam Aug 29, 2025
7d67268
Merge branch 'apache:main' into main
aicam Sep 3, 2025
cc9194d
Merge branch 'main' into permission-service
Sep 3, 2025
0199757
fix: comment
aicam Sep 3, 2025
009e88d
Merge branch 'apache:main' into main
aicam Sep 8, 2025
fcaf641
Merge branch 'main' into permission-service
aicam Sep 8, 2025
9af405f
feat: test case & delete k8s env
aicam Sep 9, 2025
525555f
fix: cuAccess
aicam Sep 9, 2025
ff5d4cc
feat: test cases
aicam Sep 10, 2025
95827e2
feat: modular auth
aicam Sep 10, 2025
6806ded
Merge branch 'apache:main' into main
aicam Sep 10, 2025
23aa44a
Merge branch 'main' into permission-service
aicam Sep 10, 2025
26e463b
Merge branch 'main' into permission-service
aicam Sep 12, 2025
5c24202
fix: comments
aicam Sep 12, 2025
414d6ec
Merge branch 'main' into permission-service
aicam Sep 12, 2025
f551267
fix: remove Authorizer trait
aicam Sep 16, 2025
85c3c55
Merge branch 'main' into permission-service
aicam Sep 16, 2025
a0c4d8a
Merge branch 'main' into permission-service
aicam Sep 18, 2025
3594355
Merge branch 'apache:main' into main
aicam Sep 18, 2025
1c8b5f0
Merge branch 'apache:main' into main
aicam Sep 29, 2025
aef1d79
Merge branch 'main' into permission-service
aicam Sep 29, 2025
b7e30d1
fix: delete
aicam Sep 29, 2025
085fcf5
fix: remove redundant code
aicam Sep 29, 2025
68c346b
Merge branch 'apache:main' into main
aicam Sep 30, 2025
1deb2f1
Merge branch 'main' into permission-service
aicam Sep 30, 2025
68959a8
fix: revert cu manage
aicam Oct 1, 2025
535042a
Merge branch 'main' into permission-service
aicam Oct 1, 2025
48d86da
fix: manage modes
aicam Oct 3, 2025
a785465
Merge branch 'main' into permission-service
aicam Oct 3, 2025
4906924
fix: comment
aicam Oct 3, 2025
5201f5c
fix: lint
aicam Oct 3, 2025
febf16e
fix: lint
aicam Oct 3, 2025
4ef1a1b
Merge branch 'main' into permission-service
aicam Oct 4, 2025
8c9833b
fix: remove formatting
aicam Oct 6, 2025
136c8a6
Merge remote-tracking branch 'origin/permission-service' into permiss…
aicam Oct 6, 2025
e5864d4
Merge branch 'main' into permission-service
aicam Oct 6, 2025
4002806
fix: remove formatting
aicam Oct 6, 2025
3dae408
Merge branch 'apache:main' into main
aicam Oct 8, 2025
ef967dd
Merge branch 'main' into permission-service
aicam Oct 8, 2025
b07f550
Merge branch 'apache:main' into main
aicam Oct 8, 2025
0ae22d0
Merge branch 'main' into permission-service
aicam Oct 8, 2025
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 @@ -21,6 +21,9 @@ package edu.uci.ics.texera.web

import com.typesafe.scalalogging.LazyLogging
import edu.uci.ics.texera.auth.JwtAuth.jwtConsumer
import edu.uci.ics.texera.auth.util.HeaderField
import edu.uci.ics.texera.config.KubernetesConfig
import edu.uci.ics.texera.dao.jooq.generated.enums.PrivilegeEnum
import edu.uci.ics.texera.dao.jooq.generated.tables.pojos.User
import org.apache.http.client.utils.URLEncodedUtils

Expand All @@ -29,6 +32,7 @@ import java.nio.charset.Charset
import javax.websocket.HandshakeResponse
import javax.websocket.server.{HandshakeRequest, ServerEndpointConfig}
import scala.jdk.CollectionConverters.ListHasAsScala
import scala.jdk.CollectionConverters._

/**
* This configurator extracts HTTPSession and associates it to ServerEndpointConfig,
Expand All @@ -46,30 +50,70 @@ class ServletAwareConfigurator extends ServerEndpointConfig.Configurator with La
response: HandshakeResponse
): Unit = {
try {
val params =
URLEncodedUtils.parse(new URI("?" + request.getQueryString), Charset.defaultCharset())
params.asScala
.map(pair => pair.getName -> pair.getValue)
.toMap
.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 headers = request.getHeaders.asScala.view.mapValues(_.asScala.headOption).toMap
if (
headers.contains(HeaderField.UserComputingUnitAccess) &&
headers.contains(HeaderField.UserId) &&
headers.contains(HeaderField.UserName) &&
headers.contains(HeaderField.UserEmail)
) {
// KUBERNETES MODE: Construct the User object from trusted headers
// coming from envoy and generated by access control service.

val userId = headers.get(HeaderField.UserId).flatten.map(_.toInt).get
val userName = headers.get(HeaderField.UserName).flatten.get
val userEmail = headers.get(HeaderField.UserEmail).flatten.get
val cuAccess = headers.get(HeaderField.UserComputingUnitAccess).flatten.getOrElse("")
config.getUserProperties.put(HeaderField.UserComputingUnitAccess, cuAccess)
logger.info(
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
)
)
logger.debug(s"User created from headers: ID=$userId, Name=$userName")
} else {
// SINGLE NODE MODE: Construct the User object from JWT in query parameters.
val params =
URLEncodedUtils.parse(new URI("?" + request.getQueryString), Charset.defaultCharset())
config.getUserProperties.put(
HeaderField.UserComputingUnitAccess,
PrivilegeEnum.WRITE.name()
)
params.asScala
.map(pair => pair.getName -> pair.getValue)
.toMap
.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
)
)
})
}
} catch {
case e: Exception =>
logger.error("Failed to retrieve the User during websocket handshake", e)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
package edu.uci.ics.texera.web

import edu.uci.ics.amber.util.JSONUtils.objectMapper
import edu.uci.ics.texera.dao.jooq.generated.enums.PrivilegeEnum
import edu.uci.ics.texera.web.model.websocket.event.TexeraWebSocketEvent
import edu.uci.ics.texera.web.service.WorkflowService
import io.reactivex.rxjava3.disposables.Disposable
Expand Down Expand Up @@ -53,6 +54,7 @@ class SessionState(session: Session) {
private var currentWorkflowState: Option[WorkflowService] = None
private var workflowSubscription = Disposable.empty()
private var executionSubscription = Disposable.empty()
private var userComputingUnitAccess: PrivilegeEnum = PrivilegeEnum.NONE

def send(msg: TexeraWebSocketEvent): Unit = {
session.getAsyncRemote.sendText(objectMapper.writeValueAsString(msg))
Expand Down Expand Up @@ -80,4 +82,9 @@ class SessionState(session: Session) {
)

}

def setUserComputingUnitAccess(cuAccess: PrivilegeEnum): Unit = {
this.userComputingUnitAccess = cuAccess
}
def getUserComputingUnitAccess: PrivilegeEnum = this.userComputingUnitAccess
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import edu.uci.ics.amber.error.ErrorUtils.getStackTraceWithAllCauses
import edu.uci.ics.amber.core.virtualidentity.WorkflowIdentity
import edu.uci.ics.amber.core.workflowruntimestate.FatalErrorType.COMPILATION_ERROR
import edu.uci.ics.amber.core.workflowruntimestate.WorkflowFatalError
import edu.uci.ics.texera.auth.util.HeaderField
import edu.uci.ics.texera.dao.jooq.generated.enums.PrivilegeEnum
import edu.uci.ics.texera.dao.jooq.generated.tables.pojos.User
import edu.uci.ics.texera.web.model.websocket.event.{WorkflowErrorEvent, WorkflowStateEvent}
import edu.uci.ics.texera.web.model.websocket.request._
Expand All @@ -51,13 +53,22 @@ class WorkflowWebsocketResource extends LazyLogging {
SessionState.setState(session.getId, sessionState)
val wid = session.getRequestParameterMap.get("wid").get(0).toLong
val cuid = session.getRequestParameterMap.get("cuid").get(0).toInt
val cuAccessEnum: PrivilegeEnum = PrivilegeEnum.valueOf(
session.getUserProperties
.get(HeaderField.UserComputingUnitAccess)
.asInstanceOf[String]
)

sessionState.setUserComputingUnitAccess(cuAccessEnum)
logger.info(
s"Websocket connection opened for workflow $wid with computing unit $cuid and access $cuAccessEnum"
)
// hack to refresh frontend run button state
sessionState.send(WorkflowStateEvent("Uninitialized"))
val workflowState =
WorkflowService.getOrCreate(WorkflowIdentity(wid), cuid)
sessionState.subscribe(workflowState)
sessionState.send(ClusterStatusUpdateEvent(ClusterListener.numWorkerNodesInCluster))
logger.info("connection open")
}

@OnClose
Expand Down Expand Up @@ -94,6 +105,9 @@ class WorkflowWebsocketResource extends LazyLogging {
sessionState.send(modifyLogicResponse)
}
case workflowExecuteRequest: WorkflowExecuteRequest =>
if (sessionState.getUserComputingUnitAccess != PrivilegeEnum.WRITE) {
throw new IllegalStateException("User does not have write access to the computing unit")
}
workflowStateOpt match {
case Some(workflow) =>
sessionState.send(WorkflowStateEvent("Initializing"))
Expand Down
1 change: 1 addition & 0 deletions core/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ lazy val CoreProject = (project in file("."))
DAO,
Config,
ConfigService,
AccessControlService,
Auth,
WorkflowCore,
ComputingUnitManagingService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,7 @@
<br />

<nz-card nzTitle="Share">
<div
style="height: 50px"
*ngIf="type !== 'computing-unit'">
<div style="height: 50px">
Access Level:
<select formControlName="accessLevel">
<option value="READ">read</option>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@
[nzPopoverTrigger]="this.config.env.userSystemEnabled?'hover':null"
[nzPopoverContent]="executionSettings"
nzPopoverPlacement="bottom"
[disabled]="runDisable || (!workflowWebsocketService.isConnected && computingUnitStatus !== ComputingUnitState.NoComputingUnit) || displayParticularWorkflowVersion"
[disabled]="runDisable || (!workflowWebsocketService.isConnected && computingUnitStatus !== ComputingUnitState.NoComputingUnit) || displayParticularWorkflowVersion || selectedComputingUnit?.accessPrivilege !== Privilege.WRITE"
id="run-button"
nz-button
nzType="primary">
Expand Down
17 changes: 16 additions & 1 deletion core/gui/src/app/workspace/component/menu/menu.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ import { ComputingUnitStatusService } from "../../service/computing-unit-status/
import { ComputingUnitState } from "../../types/computing-unit-connection.interface";
import { ComputingUnitSelectionComponent } from "../power-button/computing-unit-selection.component";
import { GuiConfigService } from "../../../common/service/gui-config.service";
import { DashboardWorkflowComputingUnit } from "../../types/workflow-computing-unit";
import { Privilege } from "../../../dashboard/type/share-access.interface";

/**
* MenuComponent is the top level menu bar that shows
Expand Down Expand Up @@ -110,6 +112,7 @@ export class MenuComponent implements OnInit, OnDestroy {

// Computing unit status variables
private computingUnitStatusSubscription: Subscription = new Subscription();
public selectedComputingUnit: DashboardWorkflowComputingUnit | null = null;
public computingUnitStatus: ComputingUnitState = ComputingUnitState.NoComputingUnit;

@ViewChild(ComputingUnitSelectionComponent) computingUnitSelectionComponent!: ComputingUnitSelectionComponent;
Expand Down Expand Up @@ -162,7 +165,8 @@ export class MenuComponent implements OnInit, OnDestroy {
this.registerWorkflowModifiableChangedHandler();
this.registerWorkflowIdUpdateHandler();

// Subscribe to computing unit status changes
// Subscribe to computing unit
this.subscribeToComputingUnitSelection();
this.subscribeToComputingUnitStatus();
}

Expand Down Expand Up @@ -202,6 +206,15 @@ export class MenuComponent implements OnInit, OnDestroy {
this.computingUnitStatusSubscription.unsubscribe();
}

private subscribeToComputingUnitSelection(): void {
this.computingUnitStatusService
.getSelectedComputingUnit()
.pipe(untilDestroyed(this))
.subscribe(unit => {
this.selectedComputingUnit = unit;
});
}

/**
* Subscribe to computing unit status changes from the ComputingUnitStatusService
*/
Expand Down Expand Up @@ -720,4 +733,6 @@ export class MenuComponent implements OnInit, OnDestroy {
this.config.env.workflowEmailNotificationEnabled && this.config.env.userSystemEnabled
);
}

protected readonly Privilege = Privilege;
}
54 changes: 54 additions & 0 deletions deployment/access-control-service.dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

FROM sbtscala/scala-sbt:eclipse-temurin-jammy-11.0.17_8_1.9.3_2.13.11 AS build

# Set working directory
WORKDIR /core

# Copy all projects under core to /core
COPY core/ .

# Update system and install dependencies
RUN apt-get update && apt-get install -y \
netcat \
unzip \
libpq-dev \
&& apt-get clean

WORKDIR /core
# Add .git for runtime calls to jgit from OPversion
COPY .git ../.git

RUN sbt clean AccessControlService/dist

# Unzip the texera binary
RUN unzip access-control-service/target/universal/access-control-service-*.zip -d target/

FROM eclipse-temurin:11-jre-jammy AS runtime

WORKDIR /core

COPY --from=build /.git /.git
# Copy the built texera binary from the build phase
COPY --from=build /core/target/access-control-service* /core/
# Copy resources directories under /core from build phase
COPY --from=build /core/access-control-service/src/main/resources /core/access-control-service/src/main/resources

CMD ["bin/access-control-service"]

EXPOSE 9096
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

apiVersion: apps/v1
kind: Deployment
metadata:
name: {{.Release.Name}}-{{ .Values.accessControlService.name }}
namespace: {{ .Release.Namespace }}
labels:
app: {{ .Release.Name }}-{{ .Values.accessControlService.name }}
spec:
replicas: {{ .Values.accessControlService.numOfPods | default 1 }}
selector:
matchLabels:
app: {{ .Release.Name }}-{{ .Values.accessControlService.name }}
template:
metadata:
labels:
app: {{ .Release.Name }}-{{ .Values.accessControlService.name }}
spec:
containers:
- name: {{ .Values.accessControlService.name }}
image: {{ .Values.accessControlService.imageName }}
imagePullPolicy: {{ .Values.texeraImages.pullPolicy }}
ports:
- containerPort: {{ .Values.accessControlService.service.port }}
env:
- name: STORAGE_JDBC_URL
value: jdbc:postgresql://{{ .Release.Name }}-postgresql:5432/texera_db?currentSchema=texera_db,public
- name: STORAGE_JDBC_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-postgresql
key: postgres-password
livenessProbe:
httpGet:
path: /api/healthcheck
port: {{ .Values.accessControlService.service.port }}
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /api/healthcheck
port: {{ .Values.accessControlService.service.port }}
initialDelaySeconds: 5
periodSeconds: 5
Loading
Loading