-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathTabcmd.cs
513 lines (428 loc) · 18 KB
/
Tabcmd.cs
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
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Windows.Forms;
using System.Xml;
using System.Runtime.Serialization.Json;
using Npgsql;
using System.Diagnostics;
using System.Net;
using System.Web;
namespace Behold_Emailer
{
class Tabcmd
{
private string tabcmd_folder;
private string username;
private string password;
private string _site;
public string site {
get { return _site; }
set { if (value.ToLower() == "default") { _site = "default"; } else { _site = value; } }
}
public string tableau_server_url;
private string repository_pw;
private string user_session_id;
private string user_auth_token;
private string tabcmd_config_location;
private string tabcmd_config_filename;
public Logger logger;
public TableauRepository repository;
public Tabcmd(string tabcmd_folder, string tableau_server_url,
string username, string password, string site, string repository_password,
string tabcmd_config_location)
{
// Tabcmd program configurations
this.tabcmd_folder = tabcmd_folder;
this.tabcmd_config_filename = "tabcmd-session.xml";
this.tabcmd_config_location = tabcmd_config_location;
// Open the configuration file and test whether it resembles the real file
try {
StreamReader tabcmd_config = new StreamReader(tabcmd_config_location + tabcmd_config_filename);
// Read first line
tabcmd_config.ReadLine();
// Read second line, should be <session>. May come up with better test
string second_line = tabcmd_config.ReadLine();
if(second_line.Contains("<session>") == false){
throw new ConfigurationException("tabcmd-session.xml file information is incorrect. File is not tabcmd-session.xml");
}
tabcmd_config.Close();
}
catch(IOException){
throw new ConfigurationException("tabcmd-config file information is incorrect. Config file could not be opened");
}
this.username = username;
this.password = password;
this.site = site;
this.tableau_server_url = tableau_server_url;
this.repository_pw = repository_password;
this.repository = new TableauRepository(this.tableau_server_url, this.repository_pw, "readonly");
// This preps tabcmd for subsequent calls
this.log("Preping the tabcmd admin session");
this.create_tabcmd_admin_session();
this.logger = null;
}
public Tabcmd(string tabcmd_folder, string tableau_server_url, string username, string password,
string site, string tabcmd_config_location, TableauRepository TableauRepository)
{
this.tabcmd_folder = tabcmd_folder;
this.username = username;
this.password = password;
this.site = site;
this.tableau_server_url = tableau_server_url;
this.tabcmd_config_filename = "tabcmd-session.xml";
this.tabcmd_config_location = tabcmd_config_location;
this.repository = TableauRepository;
// This preps tabcmd for subsequent calls
this.log("Preping the tabcmd admin session");
this.create_tabcmd_admin_session();
this.logger = null;
}
public Tabcmd(string tabcmd_folder, string tableau_server_url, string username, string password,
string site, string tabcmd_config_location, TableauRepository TableauRepository, Logger logger)
{
this.tabcmd_folder = tabcmd_folder;
this.username = username;
this.password = password;
this.site = site;
this.tableau_server_url = tableau_server_url;
this.tabcmd_config_filename = "tabcmd-session.xml";
this.tabcmd_config_location = tabcmd_config_location;
this.logger = logger;
this.repository = TableauRepository;
// This preps tabcmd for subsequent calls
this.log(String.Format("Preping the tabcmd admin session for site {0}", this.site));
this.create_tabcmd_admin_session();
}
public void log(string l)
{
if (this.logger != null)
{
this.logger.Log(l);
}
}
// All the build_x_cmd methods are just for convenience of wrapping tabcmd
public string build_directory_cmd()
{
return String.Format("cd {0}", this.tabcmd_folder);
}
public string build_login_cmd(string pw_filename)
{
try
{ // Open the text file for writing using File
File.WriteAllText(pw_filename, this.password);
}
catch (Exception e)
{
Console.WriteLine("The password file could not be read:");
Console.WriteLine(e.Message);
}
string cmd;
if ( this.site.ToLower() == "default"){
cmd = String.Format("tabcmd login -s {0} -u {1} --password-file \"{2}\"", this.tableau_server_url,
this.username, pw_filename);
}
else {
cmd = String.Format("tabcmd login -s {0} -t {1} -u {2} --password-file \"{3}\"", this.tableau_server_url,
this.site, this.username, pw_filename);
}
return cmd;
}
public string build_export_cmd(string export_type, string filename, string view_url, Dictionary<string, string> view_filter_dictionary,
bool refresh)
{
string cmd;
string[] allowable_export_types = new string[4] { "pdf", "csv", "png", "fullpdf" };
if (view_url == "")
{
throw new ConfigurationException("No view_url was provided");
}
if (allowable_export_types.Contains(export_type.ToLower()) == false)
{
// Exception
}
string additional_url_params = "";
if (view_filter_dictionary != null)
{
// WebUtility.HtmlEncode
var first_param = 0;
foreach (KeyValuePair<string, string> pair in view_filter_dictionary)
{
if (first_param == 0){
additional_url_params += "?";
first_param++;
}
else {
additional_url_params += "&";
}
// Gotta double the % sign because batch files use %2 as a replacement token.
additional_url_params += Uri.EscapeUriString(pair.Key).Replace("%","%%") + "=" + (pair.Value);
}
if (refresh == true)
{
additional_url_params += "&:refresh";
}
}
else if (view_filter_dictionary == null)
{
if (refresh == true)
{
additional_url_params += "?:refresh";
}
}
view_url += additional_url_params;
cmd = String.Format("tabcmd export \"{0}\" --filename \"{1}\" --{2}",
view_url, filename, export_type);
// Additional parameters for export options
string extra_params = "--pagelayout {4} --pagesize {5} --width {6} --height {7}";
return cmd;
}
// You need to log in to tabcmd successfully with admin privileges the first time
private void create_tabcmd_admin_session(){
this.log("Creating a tabcmd admin session");
string pw_filename = this.tabcmd_folder + "sh3zoy2lya.txt";
string[] cmds = new string[2];
cmds[0] = this.build_directory_cmd();
cmds[1] = this.build_login_cmd(pw_filename);
try
{
File.WriteAllLines("login.bat", cmds);
}
catch(IOException)
{
throw new ConfigurationException("Could not write login.bat file, please restart and check all files");
}
var results = Cmd.Run("login.bat", true);
// Check tabcmd results?
this.log(results[0]);
this.log(results[1]);
// Clear admin password file after each run
File.Delete(pw_filename);
File.Delete("login.bat");
}
public string create_export(string export_type, string view_location,
Dictionary<string, string> view_filter_dictionary, string user_to_impersonate,
string filename)
{
if (view_location == "")
{
throw new ConfigurationException("view_location is not specified");
}
if (String.Equals(user_to_impersonate, "") == false)
{
this.create_session_and_configure_tabcmd_for_user(user_to_impersonate, view_location);
}
string[] cmds = new string[2];
cmds[0] = this.build_directory_cmd();
string saved_filename;
if (String.Equals(export_type.ToLower(), "fullpdf"))
{
saved_filename = String.Format("{0}.{1}", filename, "pdf");
}
else
{
saved_filename = String.Format("{0}.{1}", filename, export_type);
}
//cmds[1] = String.Format("del \"{0}\"", saved_filename);
cmds[1] = this.build_export_cmd(export_type, saved_filename, view_location, view_filter_dictionary, false);
try
{
File.WriteAllLines("export.bat", cmds);
}
catch (IOException)
{
MessageBox.Show("Could not write export.bat file");
}
string full_file_location = this.tabcmd_folder + saved_filename;
this.log(String.Format("Writing to file {0}", full_file_location));
// Run the commands
var results = Cmd.Run("export.bat", true);
this.log(results[0]);
this.log(results[1]);
if (results[1].Contains("===== Saved"))
{
this.log("Export file generated correctly");
}
else
{
throw new ConfigurationException("Export command failed, most likely configuration issue.");
}
File.Delete("export.bat");
return full_file_location;
}
/*
* The essence of how this works is that you can use trusted tickets to create a session
* then rewrite tabcmd's config file with that session info instead. Because tabcmd has
* session continuation, you can keep running tabcmd without a signin action, but continually
* switching to the correct user
*/
/*
* tabcmd keeps a session history, stored within its XML configuration file.
* Rather than logging into tabcmd again, once there is a session history, we simply substitute in the
* impersonated user's info directly into the XML.
*/
private void configure_tabcmd_config_for_user_session(string user)
{
XmlDocument doc = new XmlDocument();
doc.Load(this.tabcmd_config_location + this.tabcmd_config_filename);
XmlWriterSettings xwsSettings = new XmlWriterSettings();
xwsSettings.Indent = true;
xwsSettings.IndentChars = " ";
XmlNode root = doc.DocumentElement;
XmlNode uname = root.SelectSingleNode("username");
uname.InnerText = user;
XmlNode baseUrl = root.SelectSingleNode("base-url");
baseUrl.InnerText = this.tableau_server_url;
XmlNode sessionId = root.SelectSingleNode("session-id");
sessionId.InnerText = this.user_session_id;
XmlNode authToken = root.SelectSingleNode("authenticity-token");
authToken.InnerText = this.user_auth_token;
XmlNode sitePrefix = root.SelectSingleNode("site-prefix");
if (this._site.ToLower() != "default")
{
sitePrefix.InnerText = String.Format("t/{0}", this._site);
}
else
{
sitePrefix.InnerText = null;
}
using (XmlWriter xwWriter = XmlWriter.Create(this.tabcmd_config_location + this.tabcmd_config_filename, xwsSettings))
{
doc.PreserveWhitespace = true;
doc.Save(xwWriter);
}
}
// By querying the sessions table, there is a JSON string which includes the auth token
private void set_tabcmd_auth_info_from_repository_for_impersonation(string username_to_impersonate)
{
NpgsqlDataReader dr = this.repository.query_sessions(username_to_impersonate);
if (dr.HasRows == true)
{
dr.Read();
this.user_session_id = dr.GetString(0);
string wg_json = dr.GetString(4);
var jsonReader = JsonReaderWriterFactory.CreateJsonReader(Encoding.UTF8.GetBytes(wg_json), new System.Xml.XmlDictionaryReaderQuotas());
var XmlDoc = new XmlDocument();
XmlDoc.Load(jsonReader);
XmlNode root = XmlDoc.DocumentElement;
XmlNode auth_token = root.SelectSingleNode("auth_token");
this.user_auth_token = auth_token.InnerText;
}
else
{
// Throw some kind of exception because you didn't find any sessions for that user
// Something must have broken in the trusted tickets stuff
}
dr.Close();
}
private void create_session_and_configure_tabcmd_for_user(string user, string view_location)
{
TableauHTTP tabhttp = new TableauHTTP(this.tableau_server_url);
tabhttp.logger = this.logger;
if (tabhttp.create_trusted_ticket_session(view_location, user, this._site, ""))
{
this.set_tabcmd_auth_info_from_repository_for_impersonation(user);
this.configure_tabcmd_config_for_user_session(user);
}
else
{
this.log("Trusted ticket session could not be established");
}
}
}
}
// Taken from http://techvalleyprojects.blogspot.com/2012/04/c-using-command-prompt.html
public static class Cmd
{
public static string[] Run(string command, bool output)
{
/*
* New array of two strings.
* string[0] is the error message.
* string[1] is the output message.
*/
string[] message = new string[2];
// ProcessStartInfo allows better control over
// the soon to executed process
ProcessStartInfo info = new ProcessStartInfo();
// Input to the process is going to come from the Streamwriter
info.RedirectStandardInput = true;
// Output from the process is going to be put into message[1]
info.RedirectStandardOutput = true;
// Error, if any, from the process is going to be put into message[0]
info.RedirectStandardError = true;
// This must be set to false
info.UseShellExecute = false;
// We want to open the command line
info.FileName = "cmd.exe";
// We don't want to see a command line window
info.CreateNoWindow = true;
// Instantiate a Process object
Process proc = new Process();
// Set the Process object's start info to the above StartProcessInfo
proc.StartInfo = info;
// Start the process
proc.Start();
// The stream writer is replacing the keyboard as the input
using (StreamWriter writer = proc.StandardInput)
{
// If the streamwriter is able to write
if (writer.BaseStream.CanWrite)
{
// Write the command that was passed into the method
writer.WriteLine(command);
// Exit the command window
writer.WriteLine("exit");
}
// close the StreamWriter
writer.Close();
}
// Get any Error's that may exist
message[0] = proc.StandardError.ReadToEnd();
// If the output flag was set to true
if (output)
{
// Get the output from the command line
message[1] = proc.StandardOutput.ReadToEnd();
}
// close the process
proc.Close();
// return the any error/output
return message;
}
public static string[] Run(string[] command, bool output)
{
string[] message = new string[2];
ProcessStartInfo info = new ProcessStartInfo();
info.RedirectStandardInput = true;
info.RedirectStandardOutput = true;
info.RedirectStandardError = true;
info.UseShellExecute = false;
info.FileName = "cmd.exe";
info.CreateNoWindow = true;
Process proc = new Process();
proc.StartInfo = info;
proc.Start();
using (StreamWriter writer = proc.StandardInput)
{
if (writer.BaseStream.CanWrite)
{
foreach (string q in command)
{
writer.WriteLine(q);
}
writer.WriteLine("exit");
}
}
message[0] = proc.StandardError.ReadToEnd();
if (output)
{
message[1] = proc.StandardOutput.ReadToEnd();
}
// close the process
proc.Close();
return message;
}
}