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
88 changes: 88 additions & 0 deletions src/common/network/odcPrivilegeElevation/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import request from '@/util/request/service';

export type ODCPrivilegePermission = {
schema?: string;
object?: string;
object_type?: 'table' | 'schema' | 'database' | 'unknown';
privilege?: string;
};

export type ODCPrivilegeApprover = {
uid: string;
name: string;
};

export type CreateODCPrivilegeElevationApplicationParams = {
datasource_uid: string;
odc_session_id?: string;
current_account_uid: string;
current_account_name_masked?: string;
sql: string;
sql_digest: string;
db_error_code?: string;
db_error_message?: string;
requested_permissions: ODCPrivilegePermission[];
reason: string;
selected_approver_uids?: string[];
};

export type ODCPrivilegeElevationStatus =
| 'SUBMITTED'
| 'APPROVED'
| 'REJECTED'
| 'EXPIRED'
| 'PROVISIONING'
| 'ELEVATED'
| 'PROVISION_FAILED';

export type ODCPrivilegeElevationApplication = {
application_uid: string;
applicant_uid?: string;
applicant_name?: string;
datasource_uid?: string;
datasource_name?: string;
project_uid?: string;
current_account_uid?: string;
current_account_name_masked?: string;
requested_permissions?: ODCPrivilegePermission[];
confirmed_permissions?: ODCPrivilegePermission[];
reason?: string;
status: ODCPrivilegeElevationStatus;
reject_reason?: string;
provision_task_uid?: string;
elevated_account_uid?: string;
elevated_account_name_masked?: string;
failure_reason?: string;
created_at?: string;
updated_at?: string;
expire_at?: string;
};

type DMSResponse<T> = {
data?: T;
code?: number;
message?: string;
errCode?: string;
errMsg?: string;
isError?: boolean;
};

export const createODCPrivilegeElevationApplication = (
params: CreateODCPrivilegeElevationApplicationParams
) =>
request.post<
DMSResponse<{
application_uid: string;
status: ODCPrivilegeElevationStatus;
approvers?: ODCPrivilegeApprover[];
expire_at?: string;
}>
>('/dms/v1/odc/privilege-elevation/applications', params, {
params: { ignoreError: true }
});

export const getODCPrivilegeElevationApplication = (applicationUID: string) =>
request.get<DMSResponse<ODCPrivilegeElevationApplication>>(
`/dms/v1/odc/privilege-elevation/applications/${applicationUID}`,
{ params: { ignoreError: true } }
);
9 changes: 8 additions & 1 deletion src/common/network/sql/executeSQL.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,14 @@ class Task {
executingSQL: data.sql,
executingSQLId: data.sqlId
});
if (data?.finished) {
const hasTerminalResult = data?.results?.some((result) =>
[
ISqlExecuteResultStatus.FAILED,
ISqlExecuteResultStatus.CANCELED,
ISqlExecuteResultStatus.SUCCESS
].includes(result?.status)
);
if (data?.finished || hasTerminalResult) {
callback(this.result);
return;
} else {
Expand Down
60 changes: 57 additions & 3 deletions src/page/Workspace/components/SQLPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,11 @@ import {
ITableColumn,
IUserConfig
} from '@/d.ts';
import { IUnauthorizedDBResources } from '@/d.ts/table';
import {
IUnauthorizedDBResources,
TablePermissionType,
UnauthorizedPermissionTypeInSQLExecute
} from '@/d.ts/table';
import {
debounceUpdatePageScriptText,
ISQLPageParams,
Expand Down Expand Up @@ -79,6 +83,34 @@ import ExecDetail from './ExecDetail';
import ExecPlan from './ExecPlan';
import styles from './index.less';

const isDBPermissionDeniedResult = (result?: ISqlExecuteResult) => {
const errorCode = String(result?.errorCode ?? '');
const errorText = [
errorCode,
result?.track,
result?.messages,
result?.statementWarnings
]
.filter(Boolean)
.join('\n');

return (
result?.status === ISqlExecuteResultStatus.FAILED &&
(errorCode === '1142' ||
/\b1142\b|command denied|access denied|permission denied|权限不足|拒绝访问/i.test(
errorText
))
);
};

const parseDeniedTableName = (sql?: string) => {
const normalizedSql = sql?.replace(/`/g, '').trim();
const matched = normalizedSql?.match(
/\b(?:from|join|update|into|table)\s+([\w$]+(?:\.[\w$]+)?)/i
);
return matched?.[1]?.split('.').pop() || '';
};

interface ISQLPageState {
resultHeight: number;
initialSQL: string;
Expand Down Expand Up @@ -777,9 +809,31 @@ export class SQLPage extends Component<IProps, ISQLPageState> {
};

public handleCheckDatabasePermission = (result: IExecuteTaskResult) => {
const deniedResult = result?.executeResult?.find(isDBPermissionDeniedResult);
const deniedSql = deniedResult?.executeSql || deniedResult?.originSql;
const session = this.getSession();
const fallbackResource: IUnauthorizedDBResources[] = deniedResult
? [
{
unauthorizedPermissionTypes: [TablePermissionType.QUERY] as any,
dataSourceId: session?.connection?.id,
projectId: null,
projectName: '',
databaseId: session?.odcDatabase?.id,
databaseName: session?.odcDatabase?.name,
tableName: parseDeniedTableName(deniedSql),
tableId: null,
applicable: false,
type: UnauthorizedPermissionTypeInSQLExecute.ODC_TABLE
}
]
: null;

this.setState({
unauthorizedResource: result?.unauthorizedDBResources,
unauthorizedSql: result?.unauthorizedSql
unauthorizedResource: result?.unauthorizedDBResources?.length
? result.unauthorizedDBResources
: fallbackResource,
unauthorizedSql: result?.unauthorizedSql || deniedSql
});
};

Expand Down
17 changes: 15 additions & 2 deletions src/page/Workspace/components/SQLResultSet/DBPermissionTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { formatMessage } from '@/util/intl';
import MultiLineOverflowText from '@/component/MultiLineOverflowText';
import { IUnauthorizedDBResources, TablePermissionType } from '@/d.ts/table';
import { CloseCircleFilled } from '@ant-design/icons';
import { Space, Tabs, Typography } from 'antd';
import { Button, Space, Tabs, Typography } from 'antd';
import styles from './index.less';
import DBPermissionTableContent from '../DBPermissionTableContent';

Expand All @@ -29,9 +29,12 @@ const PERMISSION_TAB_KEY = 'LOG';
interface IProps {
sql?: string;
dataSource: IUnauthorizedDBResources[];
showPrivilegeElevation?: boolean;
onApplyPrivilegeElevation?: () => void;
}
const DBPermissionTable: React.FC<IProps> = (props) => {
const { sql, dataSource } = props;
const { sql, dataSource, showPrivilegeElevation, onApplyPrivilegeElevation } =
props;

return (
<Tabs
Expand Down Expand Up @@ -75,6 +78,16 @@ const DBPermissionTable: React.FC<IProps> = (props) => {
})}
</Text>
</Space>
{showPrivilegeElevation && (
<div style={{ marginTop: 16 }}>
<Button type="primary" onClick={onApplyPrivilegeElevation}>
{formatMessage({
id: 'odc.privilegeElevation.applyButton',
defaultMessage: '申请权限'
})}
</Button>
</div>
)}
<div className={styles.track}>
<DBPermissionTableContent showAction dataSource={dataSource} />
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {
IUnauthorizedDBResources,
UnauthorizedPermissionTypeInSQLExecute
} from '@/d.ts/table';
import { buildRequestedPermissions, digestSql } from '../utils';

describe('PrivilegeElevationDrawer utils', () => {
it('maps unauthorized resources to requested permissions', () => {
expect(
buildRequestedPermissions([
({
unauthorizedPermissionTypes: ['QUERY' as any],
dataSourceId: 1,
projectId: 1,
projectName: 'project',
databaseId: 1,
databaseName: 'db1',
tableName: 't1',
tableId: 1,
applicable: true,
type: UnauthorizedPermissionTypeInSQLExecute.ODC_TABLE
} as unknown as IUnauthorizedDBResources)
])
).toEqual([
{
schema: 'db1',
object: 't1',
object_type: 'table',
privilege: 'SELECT'
}
]);
});

it('falls back to unknown permission and truncates sql digest', () => {
expect(buildRequestedPermissions([])).toEqual([
{ object_type: 'unknown', privilege: 'UNKNOWN' }
]);
expect(digestSql(` ${'a'.repeat(200)} `)).toHaveLength(128);
});
});
Loading