Repository navigation
Expand file tree
/
Copy pathAdvanced_009_ReadOnlyUsers.cs
More file actions
208 lines (172 loc) · 7.23 KB
/
Copy pathAdvanced_009_ReadOnlyUsers.cs
File metadata and controls
208 lines (172 loc) · 7.23 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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
using ClickHouse.Driver.Utility;
namespace ClickHouse.Driver.Examples;
/// <summary>
/// Demonstrates limitations and behavior when working with users created in READONLY = 1 mode.
/// Read-only users have significant restrictions that affect how the client can be configured.
///
/// Key limitations of READONLY = 1 users:
/// - Cannot INSERT, UPDATE, DELETE, or ALTER data
/// - Cannot modify most ClickHouse settings (including send_progress_in_http_headers)
/// - Can only query tables they have been explicitly granted access to
/// </summary>
public static class ReadOnlyUsers
{
private static readonly string TestTableName = "clickhouse_cs_readonly_test_data";
public static async Task Run()
{
Console.WriteLine("Read-Only User Examples\n");
Console.WriteLine("This example demonstrates the limitations of READONLY = 1 users.\n");
// Setup using the default (non-read-only) user
using var defaultClient = ExampleConfig.CreateHttpClient();
// Create a unique read-only user for this example
var guid = Guid.NewGuid().ToString("N");
var readOnlyUsername = $"clickhouse_cs_readonly_user_{guid}";
var readOnlyPassword = $"{guid}_pwd";
// The default JsonWriteMode sets a parameter which cannot be used by readonly users, so it must be changed
var readOnlyBuilder = ExampleConfig.HttpBuilder();
readOnlyBuilder.Username = readOnlyUsername;
readOnlyBuilder.Password = readOnlyPassword;
readOnlyBuilder["JsonWriteMode"] = "None";
string readOnlyConnectionString = readOnlyBuilder.ConnectionString;
await SetupReadOnlyUser(defaultClient, readOnlyUsername, readOnlyPassword);
await SetupTestTable(defaultClient);
Console.WriteLine($"Created read-only user: {readOnlyUsername}");
Console.WriteLine($"Created test table: {TestTableName}");
PrintSeparator();
try
{
// 1. Read-only user CAN query tables they have access to
await DemonstrateAllowedSelect(readOnlyConnectionString);
// 2. Read-only user CANNOT insert data
await DemonstrateInsertBlocked(readOnlyConnectionString);
// 3. Read-only user CANNOT query tables they don't have access to
await DemonstrateUnauthorizedTableBlocked(readOnlyConnectionString);
// 4. Read-only user CANNOT use most ClickHouse settings
await DemonstrateSettingsBlocked(readOnlyConnectionString);
Console.WriteLine("All read-only user examples completed!");
}
finally
{
// Cleanup
Console.WriteLine("\nCleaning up...");
await defaultClient.ExecuteNonQueryAsync($"DROP TABLE IF EXISTS {TestTableName}");
await defaultClient.ExecuteNonQueryAsync($"DROP USER IF EXISTS {readOnlyUsername}");
Console.WriteLine("Cleanup complete.");
}
}
private static async Task SetupReadOnlyUser(ClickHouseClient client, string username, string password)
{
// Create a read-only user with READONLY = 1
await client.ExecuteNonQueryAsync($@"
CREATE USER {username}
IDENTIFIED WITH sha256_password BY '{password}'
DEFAULT DATABASE default
SETTINGS readonly = 1
");
// Grant access only to SHOW TABLES and SELECT on the test table
await client.ExecuteNonQueryAsync($@"
GRANT SHOW TABLES, SELECT
ON {TestTableName}
TO {username}
");
}
private static async Task SetupTestTable(ClickHouseClient client)
{
await client.ExecuteNonQueryAsync($@"
CREATE OR REPLACE TABLE {TestTableName}
(id UInt64, name String)
ENGINE MergeTree()
ORDER BY (id)
");
await client.ExecuteNonQueryAsync($@"
INSERT INTO {TestTableName} VALUES
(12, 'foo'),
(42, 'bar')
");
}
/// <summary>
/// Read-only users CAN select from tables they have been granted access to.
/// </summary>
private static async Task DemonstrateAllowedSelect(string connectionString)
{
Console.WriteLine("1. Read-only user CAN query granted tables:");
using var client = new ClickHouseClient(connectionString);
using var reader = await client.ExecuteReaderAsync($"SELECT * FROM {TestTableName}");
Console.WriteLine(" Query result:");
while (reader.Read())
{
Console.WriteLine($" - id: {reader.GetFieldValue<ulong>(0)}, name: {reader.GetString(1)}");
}
PrintSeparator();
}
/// <summary>
/// Read-only users CANNOT insert data into any table.
/// </summary>
private static async Task DemonstrateInsertBlocked(string connectionString)
{
Console.WriteLine("2. Read-only user CANNOT insert data:");
using var client = new ClickHouseClient(connectionString);
try
{
await client.ExecuteNonQueryAsync($"INSERT INTO {TestTableName} VALUES (100, 'blocked')");
Console.WriteLine(" Unexpected success!");
}
catch (ClickHouseServerException ex)
{
Console.WriteLine($" [Expected error] {TruncateMessage(ex.Message)}");
}
PrintSeparator();
}
/// <summary>
/// Read-only users CANNOT query tables they don't have access to.
/// </summary>
private static async Task DemonstrateUnauthorizedTableBlocked(string connectionString)
{
Console.WriteLine("3. Read-only user CANNOT query non-granted tables (e.g., system.users):");
using var client = new ClickHouseClient(connectionString);
try
{
await client.ExecuteReaderAsync("SELECT * FROM system.users LIMIT 5");
Console.WriteLine(" Unexpected success!");
}
catch (ClickHouseServerException ex)
{
Console.WriteLine($" [Expected error] {TruncateMessage(ex.Message)}");
}
PrintSeparator();
}
/// <summary>
/// Read-only users CANNOT modify ClickHouse settings like send_progress_in_http_headers.
/// </summary>
private static async Task DemonstrateSettingsBlocked(string connectionString)
{
Console.WriteLine("4. Read-only user CANNOT use custom ClickHouse settings:");
using var client = new ClickHouseClient(connectionString);
var options = new QueryOptions
{
CustomSettings = new Dictionary<string, object>
{
["send_progress_in_http_headers"] = 1,
},
};
try
{
using var reader = await client.ExecuteReaderAsync($"SELECT * FROM {TestTableName}", options: options);
Console.WriteLine(" Unexpected success!");
}
catch (ClickHouseServerException ex)
{
Console.WriteLine($" [Expected error] {TruncateMessage(ex.Message)}");
}
PrintSeparator();
}
private static void PrintSeparator()
{
Console.WriteLine(new string('-', 72));
}
private static string TruncateMessage(string message)
{
var firstLine = message.Split('\n')[0];
return firstLine.Length > 100 ? firstLine[..100] + "..." : firstLine;
}
}