You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by la...@apache.org on 2026/06/29 19:28:58 UTC
(airavata-custos) branch identity-fixes updated (f24cbcfc5 -> f0fa32b52)
This is an automated email from the ASF dual-hosted git repository.
lahirujayathilake pushed a change to branch identity-fixes
in repository https://gitbox.apache.org/repos/asf/airavata-custos.git
from f24cbcfc5 Reshape identity error format and privilege requirement for the compute cluster extractions
new 960a0e633 Updated the project_id and user_id to VARCHAR types
new d9cb82719 Add endpoint to get the caller's user profile and effective privileges
new f0fa32b52 Add bootstrap admin email to `.env.example
The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.env.example | 3 +++
api/core.openapi.yaml | 30 +++++++++++++++++++++
.../migrations/000014_project_memberships.up.sql | 4 +--
internal/server/privilege.go | 31 ++++++++++++++++++++++
internal/server/privilege_integration_test.go | 31 ++++++++++++++++++++++
internal/server/server.go | 3 ++-
6 files changed, 99 insertions(+), 3 deletions(-)
(airavata-custos) 01/03: Updated the project_id and user_id to VARCHAR types
Posted by la...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
lahirujayathilake pushed a commit to branch identity-fixes
in repository https://gitbox.apache.org/repos/asf/airavata-custos.git
commit 960a0e633d82e8ed325e8918bfd23c9e3fb0f457
Author: lahiruj <la...@gmail.com>
AuthorDate: Mon Jun 29 14:17:20 2026 -0400
Updated the project_id and user_id to VARCHAR types
---
internal/db/migrations/000014_project_memberships.up.sql | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/internal/db/migrations/000014_project_memberships.up.sql b/internal/db/migrations/000014_project_memberships.up.sql
index 7626447e6..cd791373e 100644
--- a/internal/db/migrations/000014_project_memberships.up.sql
+++ b/internal/db/migrations/000014_project_memberships.up.sql
@@ -19,8 +19,8 @@
-- status is derived from compute_allocation_memberships, so it is not stored
-- here. One row per (project, user).
CREATE TABLE project_memberships (
- project_id CHAR(36) NOT NULL,
- user_id CHAR(36) NOT NULL,
+ project_id VARCHAR(255) NOT NULL,
+ user_id VARCHAR(255) NOT NULL,
role VARCHAR(32) NOT NULL,
added_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (project_id, user_id),
(airavata-custos) 03/03: Add bootstrap admin email to `.env.example
Posted by la...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
lahirujayathilake pushed a commit to branch identity-fixes
in repository https://gitbox.apache.org/repos/asf/airavata-custos.git
commit f0fa32b5224cb75351b1a85dbeadef0bf25ba56d
Author: lahiruj <la...@gmail.com>
AuthorDate: Mon Jun 29 15:28:52 2026 -0400
Add bootstrap admin email to `.env.example
---
.env.example | 3 +++
1 file changed, 3 insertions(+)
diff --git a/.env.example b/.env.example
index 96fbaea15..15f253d95 100644
--- a/.env.example
+++ b/.env.example
@@ -36,6 +36,9 @@ OIDC_AUDIENCE=custos-api
# Shared across connectors that need to know which cluster they belong to.
CUSTOS_CLUSTER_ID=00000000-0000-0000-0000-000000000001
+# Grants super_admin to the user with this email on first boot. Idempotent.
+CUSTOS_BOOTSTRAP_ADMIN_EMAIL=admin@custos.local
+
# --- AMIE connector (ACCESS-CI) ------------------------------------------
AMIE_BASE_URL=http://localhost:8180
(airavata-custos) 02/03: Add endpoint to get the caller's user profile and effective privileges
Posted by la...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
lahirujayathilake pushed a commit to branch identity-fixes
in repository https://gitbox.apache.org/repos/asf/airavata-custos.git
commit d9cb827199abb1fab5a29de4412df111d0ddd38b
Author: lahiruj <la...@gmail.com>
AuthorDate: Mon Jun 29 14:28:44 2026 -0400
Add endpoint to get the caller's user profile and effective privileges
---
api/core.openapi.yaml | 30 ++++++++++++++++++++++++++
internal/server/privilege.go | 31 +++++++++++++++++++++++++++
internal/server/privilege_integration_test.go | 31 +++++++++++++++++++++++++++
internal/server/server.go | 3 ++-
4 files changed, 94 insertions(+), 1 deletion(-)
diff --git a/api/core.openapi.yaml b/api/core.openapi.yaml
index c7808fbb9..65e4604b9 100644
--- a/api/core.openapi.yaml
+++ b/api/core.openapi.yaml
@@ -40,6 +40,15 @@ definitions:
- ACTIVE
- INACTIVE
- DELETED
+ CallerProfileResponse:
+ properties:
+ privileges:
+ items:
+ $ref: '#/definitions/PrivilegeKey'
+ type: array
+ user:
+ $ref: '#/definitions/User'
+ type: object
ComputeAllocation:
properties:
compute_cluster_id:
@@ -2571,6 +2580,27 @@ paths:
summary: Get a compute cluster user by (cluster, user) pair
tags:
- Compute Cluster Users
+ /me:
+ get:
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/CallerProfileResponse'
+ "401":
+ description: Missing authenticated caller
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - BearerAuth: []
+ summary: Get the caller's user record and effective privileges
+ tags:
+ - Caller
/organizations:
post:
consumes:
diff --git a/internal/server/privilege.go b/internal/server/privilege.go
index 3f1553cbb..7ecf25f71 100644
--- a/internal/server/privilege.go
+++ b/internal/server/privilege.go
@@ -45,6 +45,37 @@ func (s *Server) getCallerPrivileges(w http.ResponseWriter, r *http.Request) {
common.WriteJSON(w, http.StatusOK, map[string]any{"privileges": keys})
}
+// CallerProfileResponse bundles the caller's user row with the effective
+// privilege set.
+type CallerProfileResponse struct {
+ User *models.User `json:"user"`
+ Privileges []models.PrivilegeKey `json:"privileges"`
+}
+
+// @Summary Get the caller's user record and effective privileges
+// @Tags Caller
+// @Security BearerAuth
+// @Produce json
+// @Success 200 {object} CallerProfileResponse
+// @Failure 401 {object} object{error=string} "Missing authenticated caller"
+// @Router /me [get]
+func (s *Server) getCallerProfile(w http.ResponseWriter, r *http.Request) {
+ caller := requireCaller(w, r)
+ if caller == nil {
+ return
+ }
+ user, err := s.svc.GetUser(r.Context(), caller.UserID)
+ if err != nil {
+ common.WriteServiceError(w, err)
+ return
+ }
+ keys := identity.PrivilegesFromContext(r.Context())
+ if keys == nil {
+ keys = []models.PrivilegeKey{}
+ }
+ common.WriteJSON(w, http.StatusOK, CallerProfileResponse{User: user, Privileges: keys})
+}
+
// @Summary List the declared privilege catalog
// @Tags Privileges
// @Security BearerAuth
diff --git a/internal/server/privilege_integration_test.go b/internal/server/privilege_integration_test.go
index c8a595aab..de7f26514 100644
--- a/internal/server/privilege_integration_test.go
+++ b/internal/server/privilege_integration_test.go
@@ -83,6 +83,37 @@ func TestGetCallerPrivileges_WithGrants(t *testing.T) {
}
}
+func TestGetCallerProfile_NoCaller_401(t *testing.T) {
+ _, _, srv := setupTestStack(t)
+ rr := httptest.NewRecorder()
+ srv.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/me", nil))
+ if rr.Code != http.StatusUnauthorized {
+ t.Errorf("status: got %d, want 401", rr.Code)
+ }
+}
+
+func TestGetCallerProfile_ReturnsUserAndPrivileges(t *testing.T) {
+ database, _, srv := setupTestStack(t)
+ user := seedUser(t, database, "user@example.edu")
+ rr := httptest.NewRecorder()
+ req := httptest.NewRequest(http.MethodGet, "/me", nil)
+ req = withTestCaller(req, user, models.PrivilegeGrant)
+ srv.ServeHTTP(rr, req)
+ if rr.Code != http.StatusOK {
+ t.Fatalf("status: got %d, want 200; body=%s", rr.Code, rr.Body.String())
+ }
+ var body CallerProfileResponse
+ if err := json.NewDecoder(rr.Body).Decode(&body); err != nil {
+ t.Fatalf("decode: %v", err)
+ }
+ if body.User == nil || body.User.ID != user {
+ t.Errorf("user.id: got %+v, want %s", body.User, user)
+ }
+ if len(body.Privileges) != 1 || body.Privileges[0] != models.PrivilegeGrant {
+ t.Errorf("privileges: got %v, want [%s]", body.Privileges, models.PrivilegeGrant)
+ }
+}
+
func TestRequirePrivilege_NoGrants_403(t *testing.T) {
database, _, srv := setupTestStack(t)
user := seedUser(t, database, "user@example.edu")
diff --git a/internal/server/server.go b/internal/server/server.go
index 93860b604..4b5c1b4ac 100644
--- a/internal/server/server.go
+++ b/internal/server/server.go
@@ -164,8 +164,9 @@ func (s *Server) routes() {
s.router.RequireAuth("GET /user-identities/oidc-subjects/{oidcSub}", s.getUserIdentityByOIDCSub)
s.router.RequireAuth("GET /users/{id}/user-identities", s.listUserIdentitiesForUser)
- // Any authenticated caller may read their own effective privilege set with no privilege check.
+ // Any authenticated caller may read their own profile and effective privilege set with no privilege check.
s.router.RequireAuth("GET /user/privileges", s.getCallerPrivileges)
+ s.router.RequireAuth("GET /me", s.getCallerProfile)
s.router.RequirePrivilege("GET /privileges/catalog", models.PrivilegeGrant, s.getPrivilegeCatalog)
s.router.RequirePrivilege("GET /users/{id}/privileges", models.PrivilegeGrant, s.listUserPrivileges)
s.router.RequirePrivilege("GET /privileges/{key}/holders", models.PrivilegeGrant, s.listPrivilegeHolders)