SmartAPI
Open Source .NET RQL library for RedDot CMS / OpenText WSM Management Server
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Properties Pages
ISession.cs
Go to the documentation of this file.
1 // SmartAPI - .Net programmatic access to RedDot servers
2 //
3 // Copyright (C) 2013 erminas GbR
4 //
5 // This program is free software: you can redistribute it and/or modify it
6 // under the terms of the GNU General Public License as published by the Free Software Foundation,
7 // either version 3 of the License, or (at your option) any later version.
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11 // See the GNU General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License along with this program.
14 // If not, see <http://www.gnu.org/licenses/>.
15 
16 using System;
17 using System.Collections.Generic;
18 using System.Diagnostics;
19 using System.IO;
20 using System.Linq;
21 using System.Net;
22 using System.Reflection;
23 using System.Security.Principal;
24 using System.ServiceModel;
25 using System.Text;
26 using System.Text.RegularExpressions;
27 using System.Web;
28 using System.Xml;
29 using System.Xml.Linq;
30 using erminas.SmartAPI.CMS.Project;
31 using erminas.SmartAPI.CMS.ServerManagement;
32 using erminas.SmartAPI.Exceptions;
33 using erminas.SmartAPI.RedDotCmsXmlServer;
34 using erminas.SmartAPI.Utils;
35 using erminas.SmartAPI.Utils.CachedCollections;
36 using log4net;
37 
38 namespace erminas.SmartAPI.CMS
39 {
40  public class RunningSessionInfo
41  {
42  private readonly DateTime _lastActionDate;
43  private readonly DateTime _loginDate;
44  private readonly Guid _loginGuid;
45  private readonly string _moduleName;
46  private readonly string _projectName;
47 
48  public RunningSessionInfo(Guid loginGuid, string projectName, string moduleName, DateTime loginDate,
49  DateTime lastActionDate)
50  {
51  _loginGuid = loginGuid;
52  _projectName = projectName;
53  _moduleName = moduleName;
54  _loginDate = loginDate;
55  _lastActionDate = lastActionDate;
56  }
57 
58  internal RunningSessionInfo(XmlElement element)
59  {
60  _projectName = element.GetAttributeValue("projectname");
61  _moduleName = element.GetAttributeValue("moduledescription");
62  _loginDate = element.GetOADate("logindate").GetValueOrDefault();
63  _lastActionDate = element.GetOADate("lastactiondate").GetValueOrDefault();
64  element.TryGetGuid(out _loginGuid);
65  }
66 
67  public DateTime LastActionDate
68  {
69  get { return _lastActionDate; }
70  }
71 
72  public DateTime LoginDate
73  {
74  get { return _loginDate; }
75  }
76 
77  public Guid LoginGuid
78  {
79  get { return _loginGuid; }
80  }
81 
82  public string ModuleName
83  {
84  get { return _moduleName; }
85  }
86 
87  public string ProjectName
88  {
89  get { return _projectName; }
90  }
91  }
92 
93  public interface ISession : IDisposable
94  {
95  IUser CurrentUser { get; }
96  IndexedCachedList<string, IDialogLocale> DialogLocales { get; }
97 
98  XmlDocument ExecuteRQL(string query, RQL.IODataFormat format);
99 
105  XmlDocument ExecuteRQL(string query);
106 
113  XmlDocument ExecuteRQLInProjectContext(string query, Guid projectGuid);
114 
115  XmlDocument ExecuteRQLInProjectContextAndEmbeddedInProjectElement(string query, Guid projectGuid);
116 
123  string ExecuteRQLRaw(string query, RQL.IODataFormat ioDataFormat);
124 
133  string GetTextContent(Guid projectGuid, ILanguageVariant lang, Guid elementGuid, string typeString);
134 
138  IIndexedCachedList<int, ISystemLocale> Locales { get; }
139 
140  Guid LogonGuid { get; }
141 
146  void SelectProject(Guid projectGuid);
147 
153  void SelectProject(IProject project);
154 
158  IProject SelectedProject { get; set; }
159 
160  void SendMailFromCurrentUserAccount(EMail mail);
161  void SendMailFromSystemAccount(EMail mail);
162 
167 
168  IServerManager ServerManager { get; }
169 
170  Version ServerVersion { get; }
171 
172  string SessionKey { get; }
173 
185  Guid SetTextContent(Guid projectGuid, ILanguageVariant languageVariant, Guid textElementGuid, string typeString,
186  string content);
187 
188  ISystemLocale StandardLocale { get; }
189 
200  void WaitForAsyncProcess(TimeSpan maxWait, Predicate<IAsynchronousProcess> processPredicate);
201 
209  void WaitForAsyncProcess(TimeSpan maxWait, TimeSpan retry, Predicate<IAsynchronousProcess> processPredicate);
210  }
211 
212  public static class RQL
213  {
217  public enum IODataFormat
218  {
222  LogonGuidOnly,
223 
227  SessionKeyAndLogonGuid,
228 
232  SessionKeyInProjectElement,
233 
237  Plain,
238 
242  FormattedText,
243  SessionKeyOnly
244  }
245 
246  public const string SESSIONKEY_PLACEHOLDER = "#__SESSION_KEY__#";
247  }
248 
253  internal class Session : ISession
254  {
255  static Session()
256  {
257  //allow custom certificates
258  ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, errors) => true;
259  }
260 
261  private const string RQL_IODATA = "<IODATA>{0}</IODATA>";
262  private const string RQL_IODATA_LOGONGUID = @"<IODATA loginguid=""{0}"">{1}</IODATA>";
263  private const string RQL_IODATA_SESSIONKEY = @"<IODATA sessionkey=""{1}"" loginguid=""{0}"">{2}</IODATA>";
264  private const string RQL_IODATA_SESSIONKEY_ONLY = @"<IODATA sessionkey=""{0}"" loginguid="""">{1}</IODATA>";
265 
266  private const string RQL_IODATA_PROJECT_SESSIONKEY =
267  @"<IODATA loginguid=""{0}""><PROJECT sessionkey=""{1}"">{2}</PROJECT></IODATA>";
268 
269  private const string RQL_LOGIN =
270  @"<ADMINISTRATION action=""login"" name=""{0}"" password=""{1}""></ADMINISTRATION>";
271 
272  private const string RQL_LOGIN_FORCE =
273  @"<ADMINISTRATION action=""login"" name=""{0}"" password=""{1}"" loginguid=""{2}""/>";
274 
275  private const string RQL_SELECT_PROJECT =
276  @"<ADMINISTRATION action=""validate"" guid=""{0}"" useragent=""script""><PROJECT guid=""{1}""/></ADMINISTRATION>";
277 
278  private const string RQL_IODATA_FORMATTED_TEXT =
279  @"<IODATA loginguid=""{0}"" sessionkey=""{1}"" format=""1"">{2}</IODATA>";
280 
281  private static readonly Regex VERSION_REGEXP =
282  new Regex(
283  "(Management Server.*&nbsp;|CMS Version )\\d+(\\.\\d+)*&nbsp;Build&nbsp;(\\d+\\.\\d+\\.\\d+\\.\\d+)");
284 
285  private static readonly ILog LOG = LogManager.GetLogger("Session");
286 
287  private string _loginGuidStr;
288  private string _sessionKeyStr;
289 
290  private Session()
291  {
292  ServerManager = new ServerManager(this);
293  Locales = new IndexedCachedList<int, ISystemLocale>(GetLocales, x => x.LCID, Caching.Enabled);
294  DialogLocales = new IndexedCachedList<string, IDialogLocale>(GetDialogLocales, x => x.LanguageAbbreviation,
295  Caching.Enabled);
296 
297  }
298 
299  public Session(ServerLogin login,
300  Func<IEnumerable<RunningSessionInfo>, RunningSessionInfo> sessionReplacementSelector) : this()
301  {
302  ServerLogin = login;
303  Login(sessionReplacementSelector);
304  }
305 
309  public Session(ServerLogin login, Guid loginGuid, string sessionKey, Guid projectGuid) : this()
310  {
311  ServerLogin = login;
312  _loginGuidStr = loginGuid.ToRQLString();
313  _sessionKeyStr = sessionKey;
314 
315  InitConnection();
316  XmlElement sessionInfo = GetUserSessionInfoElement();
317  SelectedProjectGuid = sessionInfo.GetGuid("projectguid");
318  SelectProject(projectGuid);
319  }
320 
327  public IRDList<IAsynchronousProcess> AsynchronousProcesses { get { return ServerManager.AsynchronousProcesses; } }
328 
329  public IUser CurrentUser
330  {
331  get { return ServerManager.Users.Current; }
332  }
333 
334  public IndexedCachedList<string, IDialogLocale> DialogLocales { get; private set; }
335 
339  public void Dispose()
340  {
341  try
342  {
343  Logout(LogonGuid);
344 
345  // invalidate this object
346  LogonGuid = default(Guid);
347  }
348  // ReSharper disable EmptyGeneralCatchClause
349  catch
350  // ReSharper restore EmptyGeneralCatchClause
351  {
352  // exceptions are no longer relevant
353  }
354  }
355 
356  public XmlDocument ExecuteRQL(string query, RQL.IODataFormat format)
357  {
358  string result = ExecuteRQLRaw(query, format);
359  return ParseRQLResult(this, result);
360  }
361 
362  public XmlDocument ExecuteRQL(string query)
363  {
364  string result = ExecuteRQLRaw(query, RQL.IODataFormat.LogonGuidOnly);
365  try
366  {
367  var xmlDoc = new XmlDocument();
368  xmlDoc.LoadXml(result);
369  return xmlDoc;
370  } catch (Exception e)
371  {
372  throw new SmartAPIException(ServerLogin, "Illegal response from server", e);
373  }
374  }
375 
376  public XmlDocument ExecuteRQLInProjectContext(string query, Guid projectGuid)
377  {
378  SelectProject(projectGuid);
379  string result = ExecuteRQLRaw(query, RQL.IODataFormat.SessionKeyAndLogonGuid);
380  return ParseRQLResult(this, result);
381  }
382 
383  public XmlDocument ExecuteRQLInProjectContextAndEmbeddedInProjectElement(string query, Guid projectGuid)
384  {
385  SelectProject(projectGuid);
386  string result = ExecuteRQLRaw(query, RQL.IODataFormat.SessionKeyInProjectElement);
387  return ParseRQLResult(this, result);
388  }
389 
390  public string ExecuteRQLRaw(string query, RQL.IODataFormat ioDataFormat)
391  {
392  string tmpQuery = query.Replace(RQL.SESSIONKEY_PLACEHOLDER, "#" + _sessionKeyStr);
393  string rqlQuery;
394  switch (ioDataFormat)
395  {
396  case RQL.IODataFormat.SessionKeyAndLogonGuid:
397  rqlQuery = string.Format(RQL_IODATA_SESSIONKEY, _loginGuidStr, _sessionKeyStr, tmpQuery);
398  break;
399  case RQL.IODataFormat.LogonGuidOnly:
400  rqlQuery = string.Format(RQL_IODATA_LOGONGUID, _loginGuidStr, tmpQuery);
401  break;
402  case RQL.IODataFormat.Plain:
403  rqlQuery = string.Format(RQL_IODATA, tmpQuery);
404  break;
405  case RQL.IODataFormat.SessionKeyInProjectElement:
406  rqlQuery = string.Format(RQL_IODATA_PROJECT_SESSIONKEY, _loginGuidStr, _sessionKeyStr, tmpQuery);
407  break;
408  case RQL.IODataFormat.FormattedText:
409  rqlQuery = string.Format(RQL_IODATA_FORMATTED_TEXT, _loginGuidStr, _sessionKeyStr, tmpQuery);
410  break;
411 
412  case RQL.IODataFormat.SessionKeyOnly:
413  rqlQuery = string.Format(RQL_IODATA_SESSIONKEY_ONLY, _sessionKeyStr, tmpQuery);
414  break;
415  default:
416  throw new ArgumentException(String.Format("Unknown RQL.IODataFormat: {0}", ioDataFormat));
417  }
418  return SendRQLToServer(rqlQuery);
419  }
420 
429  public string GetTextContent(Guid projectGuid, ILanguageVariant lang, Guid elementGuid, string typeString)
430  {
431  const string LOAD_TEXT_CONTENT =
432  @"<IODATA loginguid=""{0}"" format=""1"" sessionkey=""{1}""><PROJECT><TEXT action=""load"" guid=""{2}"" texttype=""{3}""/></PROJECT></IODATA>";
433  SelectProject(projectGuid);
434  lang.Select();
435  return
436  SendRQLToServer(string.Format(LOAD_TEXT_CONTENT, _loginGuidStr, _sessionKeyStr,
437  elementGuid.ToRQLString(), typeString));
438  }
439 
443  public IIndexedCachedList<int, ISystemLocale> Locales { get; private set; }
444 
445  public Guid LogonGuid
446  {
447  get { return Guid.Parse(_loginGuidStr); }
448  private set { _loginGuidStr = value.ToRQLString(); }
449  }
450 
455  public void SelectProject(Guid projectGuid)
456  {
457  if (SelectedProjectGuid.Equals(projectGuid))
458  {
459  return;
460  }
461  string result;
462  RQLException exception = null;
463  try
464  {
465  result =
466  ExecuteRQLRaw(
467  string.Format(RQL_SELECT_PROJECT, _loginGuidStr, projectGuid.ToRQLString().ToUpperInvariant()),
468  RQL.IODataFormat.LogonGuidOnly);
469  } catch (RQLException e)
470  {
471  exception = e;
472  result = e.Response;
473  }
474 
475  var xmlDoc = new XmlDocument();
476  xmlDoc.LoadXml(result);
477  XmlNodeList xmlNodes = xmlDoc.GetElementsByTagName("SERVER");
478  if (xmlNodes.Count > 0)
479  {
480  SessionKey = ((XmlElement) xmlNodes[0]).GetAttributeValue("key");
481  SelectedProjectGuid = projectGuid;
482  return;
483  }
484 
485  throw new SmartAPIException(ServerLogin,
486  String.Format("Couldn't select project {0}", projectGuid.ToRQLString()),
487  exception);
488  }
489 
495  public void SelectProject(IProject project)
496  {
497  SelectProject(project.Guid);
498  }
499 
503  public IProject SelectedProject
504  {
505  get
506  {
507  return ServerManager.Users.Current.ModuleAssignment.IsServerManager
508  ? ServerManager.Projects.FirstOrDefault(x => x.Guid == SelectedProjectGuid)
509  : ServerManager.Projects.ForCurrentUser.FirstOrDefault(x => x.Guid == SelectedProjectGuid);
510  }
511  set { SelectProject(value); }
512  }
513 
514  public Guid SelectedProjectGuid { get; private set; }
515 
516  public void SendMailFromCurrentUserAccount(EMail mail)
517  {
518  SendEmail(ServerManager.Users.Current.EMail, mail);
519  }
520 
521  public void SendMailFromSystemAccount(EMail mail)
522  {
523  IApplicationServer server = ServerManager.ApplicationServers.First();
524  string fromAddress = server.From;
525 
526  SendEmail(fromAddress, mail);
527  }
528 
532  public ServerLogin ServerLogin { get; private set; }
533 
534  public IServerManager ServerManager { get; private set; }
535 
536  public Version ServerVersion { get; private set; }
537 
538  public string SessionKey
539  {
540  get
541  {
542  if (_sessionKeyStr == null)
543  {
544  throw new SmartAPIInternalException("No session key available");
545  }
546  return _sessionKeyStr;
547  }
548  private set { _sessionKeyStr = value; }
549  }
550 
560  public Guid SetTextContent(Guid projectGuid, ILanguageVariant languageVariant, Guid textElementGuid,
561  string typeString, string content)
562  {
563  const string SAVE_TEXT_CONTENT =
564  @"<IODATA loginguid=""{0}"" format=""1"" sessionkey=""{1}""><PROJECT><TEXT action=""save"" guid=""{2}"" texttype=""{3}"" >{4}</TEXT></PROJECT></IODATA>";
565  SelectProject(projectGuid);
566  languageVariant.Select();
567  string rqlResult =
568  SendRQLToServer(string.Format(SAVE_TEXT_CONTENT, _loginGuidStr, _sessionKeyStr,
569  textElementGuid == Guid.Empty ? "" : textElementGuid.ToRQLString(),
570  typeString, HttpUtility.HtmlEncode(content)));
571 
572  //in version 11.2 the server returns <guid>\r\n, but we are only interested in the guid -> Trim()
573  string resultGuidStr = XElement.Load(new StringReader(rqlResult)).Value.Trim();
574 
575  //result guid is empty, if the value was set to the same value it had before
576  if (string.IsNullOrEmpty(resultGuidStr))
577  {
578  return textElementGuid;
579  }
580 
581  Guid newGuid;
582  if (!Guid.TryParse(resultGuidStr, out newGuid) ||
583  (textElementGuid != Guid.Empty && textElementGuid != newGuid))
584  {
585  throw new SmartAPIException(ServerLogin, "Could not set text for: {0}".RQLFormat(textElementGuid));
586  }
587  return newGuid;
588  }
589 
590  public ISystemLocale StandardLocale
591  {
592  get { return Locales.First(locale => locale.IsStandardLanguage); }
593  }
594 
605  public void WaitForAsyncProcess(TimeSpan maxWait, Predicate<IAsynchronousProcess> processPredicate)
606  {
607  var retryEverySecond = new TimeSpan(0, 0, 1);
608  WaitForAsyncProcess(maxWait, retryEverySecond, processPredicate);
609  }
610 
618  public void WaitForAsyncProcess(TimeSpan maxWait, TimeSpan retry,
619  Predicate<IAsynchronousProcess> processPredicate)
620  {
621  Predicate<IRDList<IAsynchronousProcess>> pred = list => list.Any(process => processPredicate(process));
622 
623  //wait for the async process to spawn first and then wait until it is done
624 
625  DateTime start = DateTime.Now;
626  var retryEvery50ms = new TimeSpan(0, 0, 0, 0, 50);
627  AsynchronousProcesses.WaitFor(pred, maxWait, retryEvery50ms);
628 
629  TimeSpan timeLeft = maxWait - (DateTime.Now - start);
630  timeLeft = timeLeft.TotalMilliseconds > 0 ? timeLeft : new TimeSpan(0, 0, 0);
631 
632  AsynchronousProcesses.WaitFor(list => !pred(list), timeLeft, retry);
633  }
634 
635  internal XmlElement GetUserSessionInfoElement()
636  {
637  //TODO das funktioniert nur, wenn man in nem projekt drin ist
638  const string SESSION_INFO = @"<PROJECT sessionkey=""{0}""><USER action=""sessioninfo""/></PROJECT>";
639  string reply = ExecuteRQLRaw(SESSION_INFO.RQLFormat(_sessionKeyStr), RQL.IODataFormat.Plain);
640 
641  var doc = new XmlDocument();
642  doc.LoadXml(reply);
643  return (XmlElement) doc.SelectSingleNode("/IODATA/USER");
644  }
645 
646  internal void LoginToServerManager()
647  {
648  const string LOGIN_TO_SERVER_MANAGER =
649  @"<ADMINISTRATION><MODULE action=""login"" userguid=""{0}"" projectguid=""{1}"" id=""servermanager"" /></ADMINISTRATION>";
650  ExecuteRQL(LOGIN_TO_SERVER_MANAGER.RQLFormat(ServerManager.Users.Current, SelectedProjectGuid));
651  SelectedProjectGuid = Guid.Empty;
652  }
653 
654  internal static XmlDocument ParseRQLResult(ISession session, string result)
655  {
656  var xmlDoc = new XmlDocument();
657 
658  if (!result.Trim().Any())
659  {
660  return xmlDoc;
661  }
662 
663  try
664  {
665  xmlDoc.LoadXml(result);
666  return xmlDoc;
667  } catch (Exception e)
668  {
669  throw new SmartAPIException(session.ServerLogin, "Illegal response from server", e);
670  }
671  }
672 
673  private static string CheckAlreadyLoggedIn(XmlElement xmlElement)
674  {
675  return xmlElement.GetAttributeValue("loginguid") ?? "";
676  }
677 
678  private void CheckLoginResponse(XmlDocument xmlDoc,
679  Func<IEnumerable<RunningSessionInfo>, RunningSessionInfo>
680  sesssionReplacementSelector)
681  {
682  XmlNodeList xmlNodes = xmlDoc.GetElementsByTagName("LOGIN");
683 
684  if (xmlNodes.Count > 0)
685  {
686  ParseLoginResponse(xmlNodes, ServerLogin.AuthData, xmlDoc, sesssionReplacementSelector);
687  }
688  else
689  {
690  // didn't get a valid logon xml node
692  "Could not login.");
693  }
694  }
695 
696  private string CmsServerConnectionUrl { get; set; }
697 
698  private static string ExtractMessagesWithInnerExceptions(Exception e)
699  {
700  Exception curException = e;
701  var builder = new StringBuilder();
702  string linePrefix = "";
703  while (curException != null)
704  {
705  builder.Append(linePrefix);
706  builder.Append(curException.Message);
707  builder.Append("\n");
708  curException = curException.InnerException;
709  linePrefix += "* ";
710  }
711 
712  return builder.ToString();
713  }
714 
715  private List<IDialogLocale> GetDialogLocales()
716  {
717  const string LOAD_DIALOG_LANGUAGES = @"<DIALOG action=""listlanguages"" orderby=""2""/>";
718  string resultStr = ExecuteRQLRaw(LOAD_DIALOG_LANGUAGES, RQL.IODataFormat.LogonGuidOnly);
719  XmlDocument xmlDoc = ParseRQLResult(this, resultStr);
720 
721  return (from XmlElement curElement in xmlDoc.GetElementsByTagName("LIST")
722  select (IDialogLocale) new DialogLocale(this, curElement)).ToList();
723  }
724 
725  private XmlElement GetForceLoginXmlNode(PasswordAuthentication pa, Guid oldLoginGuid)
726  {
727  LOG.InfoFormat("User login will be forced. Old login guid was: {0}", oldLoginGuid.ToRQLString());
728  //hide user password in log message
729  string rql = string.Format(RQL_IODATA, RQL_LOGIN_FORCE.RQLFormat(pa.Username, pa.Password, oldLoginGuid));
730  string debugRQLOutput = string.Format(RQL_IODATA,
731  RQL_LOGIN_FORCE.RQLFormat(pa.Username, "*****", oldLoginGuid));
732  string result = SendRQLToServer(rql, debugRQLOutput);
733  var xmlDoc = new XmlDocument();
734  xmlDoc.LoadXml(result);
735  XmlNodeList xmlNodes = xmlDoc.GetElementsByTagName("LOGIN");
736  return (XmlElement) (xmlNodes.Count > 0 ? xmlNodes[0] : null);
737  }
738 
739  private List<ISystemLocale> GetLocales()
740  {
741  const string LOAD_LOCALES = @"<LANGUAGE action=""list""/>";
742  XmlDocument xmlDoc = ExecuteRQL(LOAD_LOCALES);
743  var languages = xmlDoc.GetElementsByTagName("LANGUAGES")[0] as XmlElement;
744  if (languages == null)
745  {
746  throw new SmartAPIException(ServerLogin, "Could not load languages");
747  }
748 
749  return
750  (from XmlElement item in languages.GetElementsByTagName("LIST")
751  select (ISystemLocale) new SystemLocale(this, item)).ToList();
752  }
753 
754  private XmlDocument GetLoginResponse()
755  {
757  string rql = string.Format(RQL_IODATA,
758  string.Format(RQL_LOGIN, HttpUtility.HtmlEncode(authData.Username),
759  HttpUtility.HtmlEncode(authData.Password)));
760 
761  //hide password in log messages
762  string debugOutputRQL = string.Format(RQL_IODATA,
763  string.Format(RQL_LOGIN, HttpUtility.HtmlEncode(authData.Username),
764  "*****"));
765  var xmlDoc = new XmlDocument();
766  try
767  {
768  string result = SendRQLToServer(rql, debugOutputRQL);
769  xmlDoc.LoadXml(result);
770  } catch (RQLException e)
771  {
772  if (e.ErrorCode != ErrorCode.RDError101)
773  {
774  throw e;
775  }
776  xmlDoc.LoadXml(e.Response);
777  }
778  return xmlDoc;
779  }
780 
781  private Version GetServerVersion(string baseURL)
782  {
783  string versionURI = baseURL + "ioVersionInfo.asp";
784  try
785  {
786  using (var client = new WebClient())
787  {
789  {
790  var c = new CredentialCache();
791  c.Add(new Uri(baseURL), "NTLM", ServerLogin.WindowsAuthentication);
792  client.Credentials = c;
793  }
794 
795  client.Headers.Add("Referer", baseURL);
796 
797  string responseText = client.DownloadString(versionURI);
798  Match match = VERSION_REGEXP.Match(responseText);
799  if (match.Groups.Count != 4)
800  {
802  "Could not retrieve version info of RedDot server at " +
803  baseURL + "\n" + responseText);
804  }
805 
806  return new Version(match.Groups[3].Value);
807  }
808  } catch (RedDotConnectionException)
809  {
810  throw;
811  } catch (WebException e)
812  {
814  "Could not retrieve version info of RedDot server at " + baseURL +
815  "\n" + e.Message, e);
816  } catch (Exception e)
817  {
819  "Could not retrieve version info of RedDot server at " + baseURL +
820  "\n" + e.Message, e);
821  }
822  }
823 
824  private void InitConnection()
825  {
826  string baseURL = ServerLogin.Address.ToString();
827  if (!baseURL.EndsWith("/"))
828  {
829  baseURL += "/";
830  }
831  ServerVersion = ServerLogin.ManualVersionOverride ?? GetServerVersion(baseURL);
832  CmsServerConnectionUrl = baseURL +
833  (ServerVersion.Major < 11
834  ? "webservice/RDCMSXMLServer.WSDL"
835  : "WebService/RQLWebService.svc");
836  }
837 
838  private static bool IsProjectUnavailbaleException(Exception e)
839  {
840  return
841  e.Message.Contains(
842  "The project you have selected is no longer available. Please select a different project via the Main Menu.");
843  }
844 
845  private void LoadSelectedProject(XmlDocument xmlDoc)
846  {
847  var lastModule = (XmlElement) xmlDoc.SelectSingleNode("/IODATA/USER/LASTMODULES/MODULE[@last='1']");
848  if (lastModule == null)
849  {
850  return;
851  }
852 
853  string projectStr = lastModule.GetAttributeValue("project");
854  if (!string.IsNullOrEmpty(projectStr))
855  {
856  try
857  {
858  SelectProject(Guid.Parse(projectStr));
859  } catch (SmartAPIException e)
860  {
861  if (IsProjectUnavailbaleException(e) ||
862  (e.InnerException != null && IsProjectUnavailbaleException(e.InnerException)))
863  {
864  SelectedProjectGuid = Guid.Empty;
865  }
866  else
867  {
868  throw;
869  }
870  }
871  }
872  }
873 
874  private void Login(Func<IEnumerable<RunningSessionInfo>, RunningSessionInfo> sessionReplacementSelector)
875  {
876  InitConnection();
877 
878  XmlDocument xmlDoc = GetLoginResponse();
879 
880  CheckLoginResponse(xmlDoc, sessionReplacementSelector);
881 
882  // LoadSelectedProject(xmlDoc);
883  }
884 
885  private void Logout(Guid logonGuid)
886  {
887  const string RQL_LOGOUT = @"<ADMINISTRATION><LOGOUT guid=""{0}""/></ADMINISTRATION>";
888  ExecuteRQLRaw(string.Format(RQL_LOGOUT, logonGuid.ToRQLString()), RQL.IODataFormat.LogonGuidOnly);
889  }
890 
891  private void ParseLoginResponse(XmlNodeList xmlNodes, PasswordAuthentication authData, XmlDocument xmlDoc,
892  Func<IEnumerable<RunningSessionInfo>, RunningSessionInfo>
893  sessionReplacementSelector)
894  {
895  // check if already logged in
896  var xmlNode = (XmlElement) xmlNodes[0];
897  string oldLoginGuid = CheckAlreadyLoggedIn(xmlNode);
898  if (oldLoginGuid != "")
899  {
900  RunningSessionInfo sessionToReplace;
901  if (sessionReplacementSelector == null ||
902  !TryGetSessionInfo(xmlDoc, sessionReplacementSelector, out sessionToReplace))
903  {
905  "User is already logged in and no open session was selected to get replaced");
906  }
907  xmlNode = GetForceLoginXmlNode(authData, sessionToReplace.LoginGuid);
908  if (xmlNode == null)
909  {
911  "Could not force login.");
912  }
913  }
914 
915  // here xmlNode has a valid login guid
916  string loginGuid = xmlNode.GetAttributeValue("guid");
917  if (string.IsNullOrEmpty(loginGuid))
918  {
920  "Could not login");
921  }
922  LogonGuid = Guid.Parse(loginGuid);
923  SessionKey = LogonGuid.ToRQLString();
924  LoadSelectedProject(xmlNode.OwnerDocument);
925  var loginNode = (XmlElement) xmlNodes[0];
926  string userGuidStr = loginNode.GetAttributeValue("userguid");
927  if (string.IsNullOrEmpty(userGuidStr))
928  {
929  XmlNodeList userNodes = xmlDoc.GetElementsByTagName("USER");
930  if (userNodes.Count != 1)
931  {
933  "Could not login; Invalid user data");
934  }
935  var xmlElement = ((XmlElement) userNodes[0]);
936  ((Users)ServerManager.Users).Current = new User(this, xmlElement.GetGuid()) { Name = xmlElement.GetAttributeValue("name") };
937  }
938  else
939  {
940  ((Users)ServerManager.Users).Current = new User(this, Guid.Parse(loginNode.GetAttributeValue("userguid")));
941  }
942  }
943 
944  private void SendEmail(string fromAddress, EMail mail)
945  {
946  //@"<ADMINISTRATION action=""sendmail"" to=""{0}"" subject=""{1}"" message=""{2}"" from=""{3}"" plaintext=""1"">{2}</ADMINISTRATION>";
947  const string SEND_EMAIL =
948  @"<ADMINISTRATION action=""sendmail"" to=""{0}"" subject=""{1}"" from=""{3}"" plaintext=""1"">{2}</ADMINISTRATION>";
949 
950  ExecuteRQL(SEND_EMAIL.RQLFormat(mail.To, mail.HtmlEncodedSubject, mail.HtmlEncodedMessage, fromAddress));
951  }
952 
960  private string SendRQLToServer(string rqlQuery, string debugRQLQuery = null)
961  {
962  try
963  {
964  LOG.DebugFormat("Sending RQL [{0}]: {1}", ServerLogin.Name, debugRQLQuery ?? rqlQuery);
965 
966  object error = "x";
967  object resultInfo = "";
968  var binding = new BasicHttpBinding();
969 
970  var isUsingHttps = ServerLogin.Address.Scheme.ToLowerInvariant() == "https";
971  if (isUsingHttps)
972  {
973  binding.Security.Mode = BasicHttpSecurityMode.Transport;
974  }
975  binding.ReaderQuotas.MaxStringContentLength = 2097152*10; //20MB
976  binding.ReaderQuotas.MaxArrayLength = 2097152*10; //20mb
977  binding.MaxReceivedMessageSize = 2097152*10; //20mb
978  binding.ReceiveTimeout = TimeSpan.FromMinutes(10);
979  binding.SendTimeout = TimeSpan.FromMinutes(10);
980 
982  {
983  binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Ntlm;
984  binding.Security.Mode = isUsingHttps ? BasicHttpSecurityMode.TransportWithMessageCredential : BasicHttpSecurityMode.TransportCredentialOnly;
985  }
986 
987  var add = new EndpointAddress(CmsServerConnectionUrl);
988 
989  try
990  {
991  var client = new RqlWebServiceClient(binding, add);
993  {
994  client.ClientCredentials.Windows.ClientCredential = ServerLogin.WindowsAuthentication;
995  //client.ClientCredentials.Windows.AllowNtlm = true;
996  client.ClientCredentials.Windows.AllowedImpersonationLevel =
997  TokenImpersonationLevel.Impersonation;
998  }
999  //var channel = client.ChannelFactory.CreateChannel();
1000  //var res = channel.Execute(new ExecuteRequest(rqlQuery, error, resultInfo));
1001  //var result = res.Result;
1002 
1003  string result = client.Execute(rqlQuery, ref error, ref resultInfo);
1004  string errorStr = (error ?? "").ToString();
1005  if (!string.IsNullOrEmpty(errorStr))
1006  {
1007  var exception = new RQLException(ServerLogin.Name, errorStr, result);
1008  if (exception.ErrorCode == ErrorCode.NoRight || exception.ErrorCode == ErrorCode.RDError110)
1009  {
1010  throw new MissingPrivilegesException(exception);
1011  }
1012  throw exception;
1013  }
1014  LOG.DebugFormat("Received RQL [{0}]: {1}", ServerLogin.Name, result);
1015  return result;
1016  } catch (Exception e)
1017  {
1018  string msg = ExtractMessagesWithInnerExceptions(e);
1019  LOG.Error(msg);
1020  LOG.Debug(e.StackTrace);
1021  throw;
1022  }
1023  } catch (EndpointNotFoundException e)
1024  {
1025  LOG.ErrorFormat("Server not found: {0}", CmsServerConnectionUrl);
1027  string.Format(@"Server ""{0}"" not found", CmsServerConnectionUrl),
1028  e);
1029  }
1030  }
1031 
1032  private static bool TryGetSessionInfo(XmlDocument xmlDoc,
1033  Func<IEnumerable<RunningSessionInfo>, RunningSessionInfo>
1034  sessionReplacementSelector, out RunningSessionInfo sessionToReplace)
1035  {
1036  if (sessionReplacementSelector == null)
1037  {
1038  sessionToReplace = null;
1039  return false;
1040  }
1041  Guid loginGuid;
1042  sessionToReplace =
1043  sessionReplacementSelector(from XmlElement curLogin in xmlDoc.GetElementsByTagName("LOGIN") where curLogin.TryGetGuid(out loginGuid)
1044  select new RunningSessionInfo(curLogin));
1045 
1046  return sessionToReplace != null;
1047  }
1048  }
1049 
1050  public enum UseVersioning
1051  {
1052  Yes = -1,
1053  No = 0
1054  }
1055 
1057  {
1058  TestProject = 1,
1059  LiveProject = 0
1060  }
1061 
1062  internal static class VersionVerifier
1063  {
1064  internal static void EnsureVersion(ISession session)
1065  {
1066  var stack = new StackTrace();
1067  // ReSharper disable PossibleNullReferenceException
1068  StackFrame stackFrame = stack.GetFrames()[1];
1069  // ReSharper restore PossibleNullReferenceException
1070  MethodBase methodBase = stackFrame.GetMethod();
1071  MemberInfo info = methodBase;
1072  if (methodBase.IsSpecialName && (methodBase.Name.StartsWith("get_") || methodBase.Name.StartsWith("set_")))
1073  {
1074  // ReSharper disable PossibleNullReferenceException
1075  info = methodBase.DeclaringType.GetProperty(methodBase.Name.Substring(4),
1076  //the .Substring strips get_/set_ prefixes that get generated for properties
1077  // ReSharper restore PossibleNullReferenceException
1078  BindingFlags.DeclaredOnly | BindingFlags.Public |
1079  BindingFlags.Instance | BindingFlags.NonPublic);
1080  }
1081 
1082  object[] lessThanAttributes = info.GetCustomAttributes(typeof (VersionIsLessThan), false);
1083  object[] greaterOrEqualAttributes = info.GetCustomAttributes(typeof (VersionIsGreaterThanOrEqual), false);
1084  if (lessThanAttributes.Count() != 1 && greaterOrEqualAttributes.Count() != 1)
1085  {
1086  throw new SmartAPIInternalException(string.Format("Missing version constraint attributes on {0}", info));
1087  }
1088 
1089  if (lessThanAttributes.Any())
1090  {
1091  lessThanAttributes.Cast<VersionIsLessThan>()
1092  .First()
1093  .Validate(session.ServerLogin, session.ServerVersion, info.Name);
1094  }
1095 
1096  if (greaterOrEqualAttributes.Any())
1097  {
1098  greaterOrEqualAttributes.Cast<VersionIsGreaterThanOrEqual>()
1099  .First()
1100  .Validate(session.ServerLogin, session.ServerVersion, info.Name);
1101  }
1102  }
1103  }
1104 }