Skip to content

Commit 67e0df3

Browse files
guguclaude
andcommitted
fix: show warning instead of form when cedar policy uses unsupported syntax
Detects forbid statements, when/unless clauses, and unknown actions. Falls back to code editor with a warning message. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent f17ecba commit 67e0df3

4 files changed

Lines changed: 81 additions & 4 deletions

File tree

frontend/src/app/components/users/cedar-policy-editor-dialog/cedar-policy-editor-dialog.component.css

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,22 @@
1616
text-decoration: underline;
1717
}
1818

19+
.form-parse-warning {
20+
display: flex;
21+
align-items: center;
22+
gap: 8px;
23+
padding: 12px;
24+
margin-bottom: 12px;
25+
border-radius: 4px;
26+
background: var(--mdc-theme-warning-container, #fff3e0);
27+
color: var(--mdc-theme-on-warning-container, #e65100);
28+
font-size: 13px;
29+
}
30+
31+
.form-parse-warning mat-icon {
32+
flex-shrink: 0;
33+
}
34+
1935
.code-editor-box {
2036
height: 300px;
2137
border: 1px solid var(--mdc-outlined-text-field-outline-color, rgba(0, 0, 0, 0.38));

frontend/src/app/components/users/cedar-policy-editor-dialog/cedar-policy-editor-dialog.component.html

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ <h1 mat-dialog-title>Policy — {{ data.groupTitle }}</h1>
77
</mat-button-toggle-group>
88
</div>
99

10-
<div *ngIf="editorMode === 'form'">
10+
<div *ngIf="formParseError" class="form-parse-warning">
11+
<mat-icon>warning</mat-icon>
12+
<span>This policy uses advanced Cedar syntax that cannot be represented in form mode. Please use the code editor.</span>
13+
</div>
14+
15+
<div *ngIf="editorMode === 'form' && !formParseError">
1116
<app-cedar-policy-list
1217
[policies]="policyItems"
1318
[availableTables]="availableTables"

frontend/src/app/components/users/cedar-policy-editor-dialog/cedar-policy-editor-dialog.component.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
44
import { MatButtonModule } from '@angular/material/button';
55
import { MatButtonToggleModule } from '@angular/material/button-toggle';
66
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
7+
import { MatIconModule } from '@angular/material/icon';
78
import { CodeEditorModule, CodeEditorService } from '@ngstack/code-editor';
89
import { take } from 'rxjs';
910
import { registerCedarLanguage } from 'src/app/lib/cedar-monaco-language';
1011
import { CedarPolicyItem, permissionsToPolicyItems, policyItemsToCedarPolicy } from 'src/app/lib/cedar-policy-items';
11-
import { parseCedarDashboardItems, parseCedarPolicy } from 'src/app/lib/cedar-policy-parser';
12+
import { canRepresentAsForm, parseCedarDashboardItems, parseCedarPolicy } from 'src/app/lib/cedar-policy-parser';
1213
import { normalizeTableName } from 'src/app/lib/normalize';
1314
import { TablePermission } from 'src/app/models/user';
1415
import { ConnectionsService } from 'src/app/services/connections.service';
@@ -33,7 +34,15 @@ export interface CedarPolicyEditorDialogData {
3334
selector: 'app-cedar-policy-editor-dialog',
3435
templateUrl: './cedar-policy-editor-dialog.component.html',
3536
styleUrls: ['./cedar-policy-editor-dialog.component.css'],
36-
imports: [NgIf, MatDialogModule, MatButtonModule, MatButtonToggleModule, CodeEditorModule, CedarPolicyListComponent],
37+
imports: [
38+
NgIf,
39+
MatDialogModule,
40+
MatButtonModule,
41+
MatButtonToggleModule,
42+
MatIconModule,
43+
CodeEditorModule,
44+
CedarPolicyListComponent,
45+
],
3746
})
3847
export class CedarPolicyEditorDialogComponent implements OnInit {
3948
public connectionID: string;
@@ -46,6 +55,7 @@ export class CedarPolicyEditorDialogComponent implements OnInit {
4655
public availableDashboards: AvailableDashboard[] = [];
4756
public allTables: TablePermission[] = [];
4857
public loading: boolean = true;
58+
public formParseError: boolean = false;
4959

5060
public cedarPolicyModel: object;
5161
public codeEditorOptions = {
@@ -107,7 +117,12 @@ export class CedarPolicyEditorDialogComponent implements OnInit {
107117
this.loading = false;
108118

109119
if (this.cedarPolicy) {
110-
this.policyItems = this._parseCedarToPolicyItems();
120+
this.formParseError = !canRepresentAsForm(this.cedarPolicy);
121+
if (this.formParseError) {
122+
this.editorMode = 'code';
123+
} else {
124+
this.policyItems = this._parseCedarToPolicyItems();
125+
}
111126
}
112127
});
113128
}
@@ -130,7 +145,10 @@ export class CedarPolicyEditorDialogComponent implements OnInit {
130145
uri: `cedar-policy-${this.data.groupId}-${Date.now()}.cedar`,
131146
value: this.cedarPolicy,
132147
};
148+
this.formParseError = false;
133149
} else {
150+
this.formParseError = !canRepresentAsForm(this.cedarPolicy);
151+
if (this.formParseError) return;
134152
this.policyItems = this._parseCedarToPolicyItems();
135153
}
136154

frontend/src/app/lib/cedar-policy-parser.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,44 @@ export function parseCedarDashboardItems(policyText: string, connectionId: strin
134134
return items;
135135
}
136136

137+
export function canRepresentAsForm(policyText: string): boolean {
138+
if (!policyText?.trim()) return true;
139+
140+
// Forbid statements are not supported in form mode
141+
if (/\bforbid\s*\(/.test(policyText)) return false;
142+
143+
// when/unless clauses are not supported in form mode
144+
if (/\bwhen\s*\{/.test(policyText)) return false;
145+
if (/\bunless\s*\{/.test(policyText)) return false;
146+
147+
const permits = extractPermitStatements(policyText);
148+
if (permits.length === 0) return false;
149+
150+
const knownActions = new Set([
151+
'connection:read',
152+
'connection:edit',
153+
'group:read',
154+
'group:edit',
155+
'table:*',
156+
'table:read',
157+
'table:add',
158+
'table:edit',
159+
'table:delete',
160+
'dashboard:*',
161+
'dashboard:read',
162+
'dashboard:create',
163+
'dashboard:edit',
164+
'dashboard:delete',
165+
]);
166+
167+
for (const permit of permits) {
168+
if (permit.isWildcard) continue;
169+
if (!permit.action || !knownActions.has(permit.action)) return false;
170+
}
171+
172+
return true;
173+
}
174+
137175
function extractPermitStatements(policyText: string): ParsedPermitStatement[] {
138176
const results: ParsedPermitStatement[] = [];
139177
const permitKeyword = 'permit';

0 commit comments

Comments
 (0)