-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathQueryExecutionPolicyException.java
More file actions
156 lines (137 loc) · 5.91 KB
/
Copy pathQueryExecutionPolicyException.java
File metadata and controls
156 lines (137 loc) · 5.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
package com.dbaagent.service;
import org.springframework.http.HttpStatus;
import java.util.List;
public class QueryExecutionPolicyException extends RuntimeException {
public static final String CHAT_MUTATION_BLOCKED = "CHAT_MUTATION_BLOCKED";
public static final String EDITOR_MUTATION_FORBIDDEN = "EDITOR_MUTATION_FORBIDDEN";
public static final String EDITOR_MUTATION_CONFIRMATION_REQUIRED = "EDITOR_MUTATION_CONFIRMATION_REQUIRED";
public static final String UNSAFE_MUTATION_BLOCKED = "UNSAFE_MUTATION_BLOCKED";
public static final String DB_WRITE_PRIVILEGE_DENIED = "DB_WRITE_PRIVILEGE_DENIED";
public static final String MULTI_STATEMENT_MISSING_SEMICOLONS = "MULTI_STATEMENT_MISSING_SEMICOLONS";
public static final String STATEMENT_NOT_PARSEABLE = "STATEMENT_NOT_PARSEABLE";
private final String errorCode;
private final HttpStatus httpStatus;
private final boolean requiresConfirmation;
private final String queryType;
private final List<String> warnings;
public QueryExecutionPolicyException(
String errorCode,
HttpStatus httpStatus,
String message,
boolean requiresConfirmation,
String queryType,
List<String> warnings
) {
super(message);
this.errorCode = errorCode;
this.httpStatus = httpStatus;
this.requiresConfirmation = requiresConfirmation;
this.queryType = queryType;
this.warnings = warnings == null ? List.of() : List.copyOf(warnings);
}
public static QueryExecutionPolicyException chatReadOnly(String queryType) {
return new QueryExecutionPolicyException(
CHAT_MUTATION_BLOCKED,
HttpStatus.BAD_REQUEST,
"DeepSQL chat is read-only and safe by design. I can't execute DDL or DML in chat, but I can help you prepare a safe SELECT to inspect the target rows first.",
false,
queryType,
List.of()
);
}
public static QueryExecutionPolicyException editorMutationForbidden(String queryType) {
return new QueryExecutionPolicyException(
EDITOR_MUTATION_FORBIDDEN,
HttpStatus.FORBIDDEN,
"Only admins or DBAs can execute DDL or DML from the SQL Editor. This Editor run was blocked before any database changes were attempted.",
false,
queryType,
List.of()
);
}
/**
* The statement reads as a SELECT by keyword but is not valid SQL, so DeepSQL cannot
* verify it is read-only.
*
* <p>It stays blocked — an unclassifiable statement is exactly what the guard exists
* to stop — but it is <em>not</em> DDL or DML, and saying so sent a user hunting for a
* permissions problem that did not exist. The trigger was a query pasted with the
* surrounding double quotes it had in source code: {@code QueryNormalizer.detectQueryType}
* sanitizes the prefix away and answers SELECT, while {@code isReadOnlyQuery} strips only
* comments, still sees a leading {@code "}, and answers "not read-only". Those two
* answers together mean "malformed", not "mutation".
*/
public static QueryExecutionPolicyException statementNotParseable(String queryType) {
return new QueryExecutionPolicyException(
STATEMENT_NOT_PARSEABLE,
HttpStatus.BAD_REQUEST,
"DeepSQL could not parse this statement, so it was blocked before running. "
+ "It starts like a SELECT but is not valid SQL — check for a stray quote, "
+ "bracket or backtick. SQL copied out of code or JSON often keeps the "
+ "surrounding \" characters, which makes the whole statement one quoted "
+ "identifier.",
false,
queryType,
List.of()
);
}
public static QueryExecutionPolicyException confirmationRequired(String queryType, List<String> warnings) {
return new QueryExecutionPolicyException(
EDITOR_MUTATION_CONFIRMATION_REQUIRED,
HttpStatus.OK,
"This statement will modify the database. Review the safety notes and confirm before DeepSQL submits it.",
true,
queryType,
warnings
);
}
public static QueryExecutionPolicyException unsafeMutation(String message, String queryType) {
return new QueryExecutionPolicyException(
UNSAFE_MUTATION_BLOCKED,
HttpStatus.BAD_REQUEST,
message,
false,
queryType,
List.of()
);
}
public static QueryExecutionPolicyException multiStatementMissingSemicolons() {
return new QueryExecutionPolicyException(
MULTI_STATEMENT_MISSING_SEMICOLONS,
HttpStatus.BAD_REQUEST,
"Multiple SQL statements were detected without semicolons between them. Add a semicolon (;) after each statement, or send one statement at a time.",
false,
"UNKNOWN",
List.of()
);
}
public static QueryExecutionPolicyException dbWritePrivilegeDenied(String detail, String queryType) {
return new QueryExecutionPolicyException(
DB_WRITE_PRIVILEGE_DENIED,
HttpStatus.BAD_REQUEST,
"DeepSQL submitted the statement, but the selected database connection user does not have the required write privileges. "
+ detail,
false,
queryType,
List.of()
);
}
public String getErrorCode() {
return errorCode;
}
public HttpStatus getHttpStatus() {
return httpStatus;
}
public boolean isRequiresConfirmation() {
return requiresConfirmation;
}
public String getQueryType() {
return queryType;
}
public List<String> getWarnings() {
return warnings;
}
public boolean isChatReadOnlyBlock() {
return CHAT_MUTATION_BLOCKED.equals(errorCode);
}
}