mirror of
https://github.com/apache/superset.git
synced 2026-07-12 01:35:36 +00:00
170 lines
5.2 KiB
Python
170 lines
5.2 KiB
Python
# 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 __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
if TYPE_CHECKING:
|
|
from flask_appbuilder.security.sqla.models import Role, User
|
|
from sqlalchemy.sql import CompoundSelect
|
|
|
|
from sqlalchemy import select, union_all
|
|
|
|
from superset import db
|
|
from superset.subjects.models import Subject
|
|
from superset.subjects.types import SubjectType
|
|
|
|
|
|
def get_user_subject_ids_subquery(user_id: int) -> CompoundSelect:
|
|
"""Return a SQLAlchemy Select of all Subject IDs for a user (not executed).
|
|
|
|
Includes:
|
|
1. The user's own USER-type subject
|
|
2. ROLE-type subjects for all direct and group-derived roles the user has
|
|
3. GROUP-type subjects for all groups the user belongs to
|
|
"""
|
|
from flask_appbuilder.security.sqla.models import (
|
|
assoc_group_role,
|
|
assoc_user_group,
|
|
assoc_user_role,
|
|
)
|
|
|
|
user_subj = select(Subject.id).where(Subject.user_id == user_id)
|
|
|
|
role_subj = (
|
|
select(Subject.id)
|
|
.join(
|
|
assoc_user_role,
|
|
Subject.role_id == assoc_user_role.c.role_id,
|
|
)
|
|
.where(assoc_user_role.c.user_id == user_id)
|
|
)
|
|
|
|
group_subj = (
|
|
select(Subject.id)
|
|
.join(
|
|
assoc_user_group,
|
|
Subject.group_id == assoc_user_group.c.group_id,
|
|
)
|
|
.where(assoc_user_group.c.user_id == user_id)
|
|
)
|
|
|
|
group_role_subj = (
|
|
select(Subject.id)
|
|
.join(
|
|
assoc_group_role,
|
|
Subject.role_id == assoc_group_role.c.role_id,
|
|
)
|
|
.join(
|
|
assoc_user_group,
|
|
assoc_group_role.c.group_id == assoc_user_group.c.group_id,
|
|
)
|
|
.where(assoc_user_group.c.user_id == user_id)
|
|
)
|
|
|
|
return union_all(user_subj, role_subj, group_subj, group_role_subj)
|
|
|
|
|
|
def get_user_subject_ids(user_id: int) -> list[int]:
|
|
"""Return all Subject IDs that a user represents.
|
|
|
|
Includes:
|
|
1. The user's own USER-type subject
|
|
2. ROLE-type subjects for all direct and group-derived roles the user has
|
|
3. GROUP-type subjects for all groups the user belongs to
|
|
"""
|
|
result = db.session.execute(get_user_subject_ids_subquery(user_id))
|
|
return [row[0] for row in result]
|
|
|
|
|
|
def get_current_user_subject_ids() -> list[int]:
|
|
"""Return Subject IDs represented by the current request principal."""
|
|
from flask import g
|
|
|
|
from superset.utils.core import get_user_id
|
|
|
|
user = getattr(g, "user", None)
|
|
if getattr(user, "is_guest_user", False):
|
|
return [
|
|
subject.id for subject in subjects_from_roles(getattr(user, "roles", []))
|
|
]
|
|
|
|
user_id = get_user_id()
|
|
return get_user_subject_ids(user_id) if user_id else []
|
|
|
|
|
|
def get_user_subject(user_id: int) -> Subject | None:
|
|
"""Get the USER-type Subject for a given user ID."""
|
|
return (
|
|
db.session.query(Subject)
|
|
.filter_by(
|
|
user_id=user_id,
|
|
type=SubjectType.USER,
|
|
)
|
|
.first()
|
|
)
|
|
|
|
|
|
def get_or_create_user_subject(user_id: int) -> Subject | None:
|
|
"""Get or create the USER-type Subject for a given user ID."""
|
|
if subject := get_user_subject(user_id):
|
|
return subject
|
|
|
|
from superset import security_manager
|
|
from superset.subjects.sync import sync_user_subject
|
|
|
|
user = db.session.get(security_manager.user_model, user_id)
|
|
if not user:
|
|
return None
|
|
|
|
sync_user_subject(user)
|
|
db.session.flush()
|
|
return get_user_subject(user_id)
|
|
|
|
|
|
def get_subject(id_: int) -> Subject | None:
|
|
"""Look up a Subject by its primary key."""
|
|
return db.session.get(Subject, id_)
|
|
|
|
|
|
def subjects_from_roles(roles: list[Role | int]) -> list[Subject]:
|
|
"""Convert a list of Role objects or role IDs to role-type Subjects.
|
|
|
|
Bridges the legacy ``roles`` to the Subject model.
|
|
Accepts both Role objects and plain integer IDs.
|
|
Silently skips roles without a matching Subject row.
|
|
"""
|
|
subjects = []
|
|
for role in roles:
|
|
role_id = role if isinstance(role, int) else role.id
|
|
subj = (
|
|
db.session.query(Subject)
|
|
.filter_by(role_id=role_id, type=SubjectType.ROLE)
|
|
.first()
|
|
)
|
|
if subj:
|
|
subjects.append(subj)
|
|
return subjects
|
|
|
|
|
|
def get_user_label(user: User) -> str:
|
|
"""Return 'First Last' display name, falling back to username."""
|
|
first = getattr(user, "first_name", None) or ""
|
|
last = getattr(user, "last_name", None) or ""
|
|
full = f"{first} {last}".strip()
|
|
return full or user.username
|