mirror of
https://github.com/mRemoteNG/mRemoteNG.git
synced 2026-02-17 14:07:46 +08:00
Merge pull request #2591 from tecxx/develop-orig
add Clickstudios Passwordstate API connector
This commit is contained in:
BIN
ExternalConnectors/CPS/CPS.ico
Normal file
BIN
ExternalConnectors/CPS/CPS.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
241
ExternalConnectors/CPS/CPSConnectionForm.Designer.cs
generated
Normal file
241
ExternalConnectors/CPS/CPSConnectionForm.Designer.cs
generated
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
namespace ExternalConnectors.CPS
|
||||||
|
{
|
||||||
|
partial class CPSConnectionForm
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Required designer variable.
|
||||||
|
/// </summary>
|
||||||
|
private System.ComponentModel.IContainer components = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clean up any resources being used.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing && (components != null))
|
||||||
|
{
|
||||||
|
components.Dispose();
|
||||||
|
}
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Windows Form Designer generated code
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Required method for Designer support - do not modify
|
||||||
|
/// the contents of this method with the code editor.
|
||||||
|
/// </summary>
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(CPSConnectionForm));
|
||||||
|
tbServerURL = new TextBox();
|
||||||
|
label3 = new Label();
|
||||||
|
tbAPIKey = new TextBox();
|
||||||
|
btnOK = new Button();
|
||||||
|
btnCancel = new Button();
|
||||||
|
tableLayoutPanel1 = new TableLayoutPanel();
|
||||||
|
label1 = new Label();
|
||||||
|
label6 = new Label();
|
||||||
|
tbOTP = new TextBox();
|
||||||
|
cbUseSSO = new CheckBox();
|
||||||
|
tableLayoutPanel2 = new TableLayoutPanel();
|
||||||
|
label4 = new Label();
|
||||||
|
tableLayoutPanel1.SuspendLayout();
|
||||||
|
tableLayoutPanel2.SuspendLayout();
|
||||||
|
SuspendLayout();
|
||||||
|
//
|
||||||
|
// tbServerURL
|
||||||
|
//
|
||||||
|
tbServerURL.Dock = DockStyle.Fill;
|
||||||
|
tbServerURL.Location = new Point(298, 5);
|
||||||
|
tbServerURL.Margin = new Padding(5);
|
||||||
|
tbServerURL.Name = "tbServerURL";
|
||||||
|
tbServerURL.Size = new Size(611, 27);
|
||||||
|
tbServerURL.TabIndex = 0;
|
||||||
|
//
|
||||||
|
// label3
|
||||||
|
//
|
||||||
|
label3.AutoSize = true;
|
||||||
|
label3.Dock = DockStyle.Fill;
|
||||||
|
label3.Location = new Point(5, 84);
|
||||||
|
label3.Margin = new Padding(5, 0, 5, 0);
|
||||||
|
label3.Name = "label3";
|
||||||
|
label3.Size = new Size(283, 42);
|
||||||
|
label3.TabIndex = 5;
|
||||||
|
label3.Text = "API Key";
|
||||||
|
label3.TextAlign = ContentAlignment.MiddleLeft;
|
||||||
|
//
|
||||||
|
// tbAPIKey
|
||||||
|
//
|
||||||
|
tbAPIKey.Dock = DockStyle.Fill;
|
||||||
|
tbAPIKey.Location = new Point(298, 89);
|
||||||
|
tbAPIKey.Margin = new Padding(5);
|
||||||
|
tbAPIKey.Name = "tbAPIKey";
|
||||||
|
tbAPIKey.Size = new Size(611, 27);
|
||||||
|
tbAPIKey.TabIndex = 4;
|
||||||
|
tbAPIKey.UseSystemPasswordChar = true;
|
||||||
|
//
|
||||||
|
// btnOK
|
||||||
|
//
|
||||||
|
btnOK.Anchor = AnchorStyles.Right;
|
||||||
|
btnOK.DialogResult = DialogResult.OK;
|
||||||
|
btnOK.Location = new Point(337, 16);
|
||||||
|
btnOK.Margin = new Padding(5);
|
||||||
|
btnOK.Name = "btnOK";
|
||||||
|
btnOK.Size = new Size(101, 35);
|
||||||
|
btnOK.TabIndex = 6;
|
||||||
|
btnOK.Text = "OK";
|
||||||
|
btnOK.UseVisualStyleBackColor = true;
|
||||||
|
//
|
||||||
|
// btnCancel
|
||||||
|
//
|
||||||
|
btnCancel.Anchor = AnchorStyles.Left;
|
||||||
|
btnCancel.DialogResult = DialogResult.Cancel;
|
||||||
|
btnCancel.Location = new Point(474, 16);
|
||||||
|
btnCancel.Margin = new Padding(5);
|
||||||
|
btnCancel.Name = "btnCancel";
|
||||||
|
btnCancel.Size = new Size(101, 35);
|
||||||
|
btnCancel.TabIndex = 11;
|
||||||
|
btnCancel.Text = "Cancel";
|
||||||
|
btnCancel.UseVisualStyleBackColor = true;
|
||||||
|
//
|
||||||
|
// tableLayoutPanel1
|
||||||
|
//
|
||||||
|
tableLayoutPanel1.ColumnCount = 2;
|
||||||
|
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 32.06997F));
|
||||||
|
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 67.93003F));
|
||||||
|
tableLayoutPanel1.Controls.Add(label1, 0, 0);
|
||||||
|
tableLayoutPanel1.Controls.Add(label3, 0, 2);
|
||||||
|
tableLayoutPanel1.Controls.Add(tbServerURL, 1, 0);
|
||||||
|
tableLayoutPanel1.Controls.Add(tbAPIKey, 1, 2);
|
||||||
|
tableLayoutPanel1.Controls.Add(label6, 0, 3);
|
||||||
|
tableLayoutPanel1.Controls.Add(tbOTP, 1, 3);
|
||||||
|
tableLayoutPanel1.Controls.Add(cbUseSSO, 0, 1);
|
||||||
|
tableLayoutPanel1.Dock = DockStyle.Top;
|
||||||
|
tableLayoutPanel1.Location = new Point(0, 0);
|
||||||
|
tableLayoutPanel1.Margin = new Padding(5);
|
||||||
|
tableLayoutPanel1.Name = "tableLayoutPanel1";
|
||||||
|
tableLayoutPanel1.RowCount = 5;
|
||||||
|
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 20F));
|
||||||
|
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 20F));
|
||||||
|
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 20F));
|
||||||
|
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 20F));
|
||||||
|
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 20F));
|
||||||
|
tableLayoutPanel1.Size = new Size(914, 212);
|
||||||
|
tableLayoutPanel1.TabIndex = 12;
|
||||||
|
//
|
||||||
|
// label1
|
||||||
|
//
|
||||||
|
label1.AutoSize = true;
|
||||||
|
label1.Dock = DockStyle.Fill;
|
||||||
|
label1.Location = new Point(5, 0);
|
||||||
|
label1.Margin = new Padding(5, 0, 5, 0);
|
||||||
|
label1.Name = "label1";
|
||||||
|
label1.Size = new Size(283, 42);
|
||||||
|
label1.TabIndex = 2;
|
||||||
|
label1.Text = "Passwordstate URL";
|
||||||
|
label1.TextAlign = ContentAlignment.MiddleLeft;
|
||||||
|
//
|
||||||
|
// label6
|
||||||
|
//
|
||||||
|
label6.AutoSize = true;
|
||||||
|
label6.Dock = DockStyle.Fill;
|
||||||
|
label6.Location = new Point(3, 126);
|
||||||
|
label6.Name = "label6";
|
||||||
|
label6.Size = new Size(287, 42);
|
||||||
|
label6.TabIndex = 15;
|
||||||
|
label6.Text = "2FA OTP (Optional)";
|
||||||
|
//
|
||||||
|
// tbOTP
|
||||||
|
//
|
||||||
|
tbOTP.Dock = DockStyle.Fill;
|
||||||
|
tbOTP.Location = new Point(298, 131);
|
||||||
|
tbOTP.Margin = new Padding(5);
|
||||||
|
tbOTP.Name = "tbOTP";
|
||||||
|
tbOTP.Size = new Size(611, 27);
|
||||||
|
tbOTP.TabIndex = 5;
|
||||||
|
//
|
||||||
|
// cbUseSSO
|
||||||
|
//
|
||||||
|
cbUseSSO.Anchor = AnchorStyles.Left;
|
||||||
|
cbUseSSO.AutoSize = true;
|
||||||
|
cbUseSSO.Location = new Point(5, 53);
|
||||||
|
cbUseSSO.Margin = new Padding(5, 5, 5, 0);
|
||||||
|
cbUseSSO.Name = "cbUseSSO";
|
||||||
|
cbUseSSO.Size = new Size(157, 24);
|
||||||
|
cbUseSSO.TabIndex = 14;
|
||||||
|
cbUseSSO.Text = "Use SSO / WinAuth";
|
||||||
|
cbUseSSO.UseVisualStyleBackColor = true;
|
||||||
|
cbUseSSO.CheckedChanged += cbUseSSO_CheckedChanged;
|
||||||
|
//
|
||||||
|
// tableLayoutPanel2
|
||||||
|
//
|
||||||
|
tableLayoutPanel2.ColumnCount = 5;
|
||||||
|
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 106F));
|
||||||
|
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
|
||||||
|
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 26F));
|
||||||
|
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
|
||||||
|
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 107F));
|
||||||
|
tableLayoutPanel2.Controls.Add(btnOK, 1, 0);
|
||||||
|
tableLayoutPanel2.Controls.Add(btnCancel, 3, 0);
|
||||||
|
tableLayoutPanel2.Dock = DockStyle.Bottom;
|
||||||
|
tableLayoutPanel2.Location = new Point(0, 300);
|
||||||
|
tableLayoutPanel2.Margin = new Padding(5);
|
||||||
|
tableLayoutPanel2.Name = "tableLayoutPanel2";
|
||||||
|
tableLayoutPanel2.RowCount = 1;
|
||||||
|
tableLayoutPanel2.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
|
||||||
|
tableLayoutPanel2.Size = new Size(914, 67);
|
||||||
|
tableLayoutPanel2.TabIndex = 13;
|
||||||
|
//
|
||||||
|
// label4
|
||||||
|
//
|
||||||
|
label4.AutoSize = true;
|
||||||
|
label4.Dock = DockStyle.Fill;
|
||||||
|
label4.Location = new Point(0, 212);
|
||||||
|
label4.Margin = new Padding(5, 0, 5, 0);
|
||||||
|
label4.Name = "label4";
|
||||||
|
label4.Size = new Size(345, 20);
|
||||||
|
label4.TabIndex = 14;
|
||||||
|
label4.Text = "URL is the base URL, like https://pass.domain.local/";
|
||||||
|
label4.TextAlign = ContentAlignment.MiddleLeft;
|
||||||
|
//
|
||||||
|
// CPSConnectionForm
|
||||||
|
//
|
||||||
|
AcceptButton = btnOK;
|
||||||
|
AutoScaleDimensions = new SizeF(8F, 20F);
|
||||||
|
AutoScaleMode = AutoScaleMode.Font;
|
||||||
|
ClientSize = new Size(914, 367);
|
||||||
|
Controls.Add(label4);
|
||||||
|
Controls.Add(tableLayoutPanel2);
|
||||||
|
Controls.Add(tableLayoutPanel1);
|
||||||
|
Icon = (Icon)resources.GetObject("$this.Icon");
|
||||||
|
Margin = new Padding(5);
|
||||||
|
Name = "CPSConnectionForm";
|
||||||
|
Text = "Passwordstate API Login Data";
|
||||||
|
Activated += CPSConnectionForm_Activated;
|
||||||
|
tableLayoutPanel1.ResumeLayout(false);
|
||||||
|
tableLayoutPanel1.PerformLayout();
|
||||||
|
tableLayoutPanel2.ResumeLayout(false);
|
||||||
|
ResumeLayout(false);
|
||||||
|
PerformLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
private System.Windows.Forms.Label label3;
|
||||||
|
|
||||||
|
public System.Windows.Forms.TextBox tbServerURL;
|
||||||
|
//public System.Windows.Forms.TextBox tbUsername;
|
||||||
|
public System.Windows.Forms.TextBox tbAPIKey;
|
||||||
|
private System.Windows.Forms.Button btnOK;
|
||||||
|
private System.Windows.Forms.Button btnCancel;
|
||||||
|
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
|
||||||
|
private System.Windows.Forms.Label label2;
|
||||||
|
private System.Windows.Forms.Label label1;
|
||||||
|
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel2;
|
||||||
|
public System.Windows.Forms.CheckBox cbUseSSO;
|
||||||
|
private System.Windows.Forms.Label label4;
|
||||||
|
private Label label6;
|
||||||
|
public TextBox tbOTP;
|
||||||
|
}
|
||||||
|
}
|
||||||
41
ExternalConnectors/CPS/CPSConnectionForm.cs
Normal file
41
ExternalConnectors/CPS/CPSConnectionForm.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
namespace ExternalConnectors.CPS
|
||||||
|
{
|
||||||
|
public partial class CPSConnectionForm : Form
|
||||||
|
{
|
||||||
|
public CPSConnectionForm()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CPSConnectionForm_Activated(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
SetVisibility();
|
||||||
|
if (cbUseSSO.Checked)
|
||||||
|
btnOK.Focus();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (tbAPIKey.Text.Length == 0)
|
||||||
|
tbAPIKey.Focus();
|
||||||
|
else
|
||||||
|
tbOTP.Focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
tbAPIKey.Focus();
|
||||||
|
if (!string.IsNullOrEmpty(tbAPIKey.Text) || cbUseSSO.Checked == true)
|
||||||
|
tbOTP.Focus();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cbUseSSO_CheckedChanged(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
SetVisibility();
|
||||||
|
}
|
||||||
|
private void SetVisibility()
|
||||||
|
{
|
||||||
|
bool ch = cbUseSSO.Checked;
|
||||||
|
tbAPIKey.Enabled = !ch;
|
||||||
|
//tbUsername.Enabled = !ch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
149
ExternalConnectors/CPS/CPSConnectionForm.resx
Normal file
149
ExternalConnectors/CPS/CPSConnectionForm.resx
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
|
||||||
|
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>
|
||||||
|
AAABAAEAEBAAAAEACABoBQAAFgAAACgAAAAQAAAAIAAAAAEACAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAA
|
||||||
|
AAAtLDAA+PDaAP///wD79ugA/PrzAPf39wBGRUkA7NacAM2WAAA3z6kA+Pz/AIKBgwD+//4A4sNtAHXe
|
||||||
|
xAD8+vIAjuTOANOjHgDV6/4AJZf3APn5+QDw37IAIB8jAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAgICAgICCQ4CAgIWAgIUAgICAgICAgkJAgICFhYWAAIIDwICAgICCQwCAhYWFgYCCAgIBwIC
|
||||||
|
AgkJAgsWFhYWBQICCAgIAwIJCQIWFhYWAgICAgINCAgCAgICFhYCAgICAgICAgIRAgICAgICAgICAgIC
|
||||||
|
AgICEwICAgICEhMTExMCAgITExMCAgICAgITExMTAhMTExMCAgICAgICAgICAhMTEwICAgIICAIJCQIC
|
||||||
|
AgIKAgICAgIICAQCAgkJAgICAgICAgICCAgCAgICCQkCAgICAgICFQgBAgICAgwJAgICAgICAggIAgIC
|
||||||
|
AgICEAkCAgICAgIIAgICAgICAgICAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
|
||||||
|
</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
301
ExternalConnectors/CPS/PasswordstateInterface.cs
Normal file
301
ExternalConnectors/CPS/PasswordstateInterface.cs
Normal file
@@ -0,0 +1,301 @@
|
|||||||
|
using Microsoft.Win32;
|
||||||
|
using Org.BouncyCastle.Crypto;
|
||||||
|
using Org.BouncyCastle.Crypto.Parameters;
|
||||||
|
using Org.BouncyCastle.OpenSsl;
|
||||||
|
using Org.BouncyCastle.Security;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
|
||||||
|
namespace ExternalConnectors.CPS;
|
||||||
|
|
||||||
|
public class PasswordstateInterface
|
||||||
|
{
|
||||||
|
private static class CPSConnectionData
|
||||||
|
{
|
||||||
|
public static string ssUsername = "";
|
||||||
|
public static string ssPassword = "";
|
||||||
|
public static string ssUrl = "";
|
||||||
|
public static string ssOTP = "";
|
||||||
|
public static DateTime ssOTPTimeStampExpiration;
|
||||||
|
|
||||||
|
public static bool ssSSO = false;
|
||||||
|
public static bool initdone = false;
|
||||||
|
|
||||||
|
//token
|
||||||
|
//public static string ssTokenBearer = "";
|
||||||
|
//public static DateTime ssTokenExpiresOn = DateTime.UtcNow;
|
||||||
|
//public static string ssTokenRefresh = "";
|
||||||
|
|
||||||
|
public static void Init()
|
||||||
|
{
|
||||||
|
// 2024-05-04 passwordstate currently does not support auth tokens, so we need to re-enter otp codes frequently
|
||||||
|
if (!string.IsNullOrEmpty(ssOTP) && DateTime.Now > ssOTPTimeStampExpiration)
|
||||||
|
{
|
||||||
|
ssOTP = "";
|
||||||
|
initdone = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (initdone == true)
|
||||||
|
return;
|
||||||
|
|
||||||
|
RegistryKey key = Registry.CurrentUser.CreateSubKey(@"SOFTWARE\mRemoteCPSInterface");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// display gui and ask for data
|
||||||
|
CPSConnectionForm f = new CPSConnectionForm();
|
||||||
|
//string? un = key.GetValue("Username") as string;
|
||||||
|
//f.tbUsername.Text = un ?? "";
|
||||||
|
f.tbAPIKey.Text = CPSConnectionData.ssPassword; // in OTP refresh cases, this value might already be filled
|
||||||
|
|
||||||
|
string? url = key.GetValue("URL") as string;
|
||||||
|
if (url == null || !url.Contains("://"))
|
||||||
|
url = "https://cred.domain.local/SecretServer";
|
||||||
|
f.tbServerURL.Text = url;
|
||||||
|
|
||||||
|
var b = key.GetValue("SSO");
|
||||||
|
if (b == null || (string)b != "True")
|
||||||
|
ssSSO = false;
|
||||||
|
else
|
||||||
|
ssSSO = true;
|
||||||
|
f.cbUseSSO.Checked = ssSSO;
|
||||||
|
|
||||||
|
// show dialog
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
_ = f.ShowDialog();
|
||||||
|
|
||||||
|
if (f.DialogResult != DialogResult.OK)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// store values to memory
|
||||||
|
//ssUsername = f.tbUsername.Text;
|
||||||
|
ssPassword = f.tbAPIKey.Text;
|
||||||
|
ssUrl = f.tbServerURL.Text;
|
||||||
|
ssSSO = f.cbUseSSO.Checked;
|
||||||
|
ssOTP = f.tbOTP.Text;
|
||||||
|
ssOTPTimeStampExpiration = DateTime.Now.AddSeconds(30);
|
||||||
|
// check connection first
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (TestCredentials() == true)
|
||||||
|
{
|
||||||
|
initdone = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
MessageBox.Show("Test Credentials failed - please check your credentials");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// write values to registry
|
||||||
|
//key.SetValue("Username", ssUsername);
|
||||||
|
key.SetValue("URL", ssUrl);
|
||||||
|
key.SetValue("SSO", ssSSO);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
key.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TestCredentials()
|
||||||
|
{
|
||||||
|
return ConnectionTest();
|
||||||
|
}
|
||||||
|
private static bool ConnectionTest()
|
||||||
|
{
|
||||||
|
if (CPSConnectionData.ssSSO)
|
||||||
|
{
|
||||||
|
string url = $"{CPSConnectionData.ssUrl}/winapi/passwordlists/";
|
||||||
|
|
||||||
|
using HttpClient client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true });
|
||||||
|
client.DefaultRequestHeaders.Accept.Clear();
|
||||||
|
client.DefaultRequestHeaders.Add("User-Agent", "mRemote");
|
||||||
|
client.DefaultRequestHeaders.Add("OTP", CPSConnectionData.ssOTP);
|
||||||
|
|
||||||
|
var json = client.GetStringAsync(url).Result;
|
||||||
|
JsonNode? data = JsonSerializer.Deserialize<JsonNode>(json);
|
||||||
|
if (data == null)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string url = $"{CPSConnectionData.ssUrl}/api/passwordlists/";
|
||||||
|
using HttpClient client = new HttpClient();
|
||||||
|
client.DefaultRequestHeaders.Accept.Clear();
|
||||||
|
client.DefaultRequestHeaders.Add("User-Agent", "mRemote");
|
||||||
|
client.DefaultRequestHeaders.Add("APIKey", CPSConnectionData.ssPassword);
|
||||||
|
client.DefaultRequestHeaders.Add("OTP", CPSConnectionData.ssOTP);
|
||||||
|
|
||||||
|
var json = client.GetStringAsync(url).Result;
|
||||||
|
JsonNode? data = JsonSerializer.Deserialize<JsonNode>(json);
|
||||||
|
if (data == null)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JsonNode? FetchDataWinAuth(int secretID)
|
||||||
|
{
|
||||||
|
string url = $"{CPSConnectionData.ssUrl}/winapi/passwords/{secretID}";
|
||||||
|
|
||||||
|
using HttpClient client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true });
|
||||||
|
client.DefaultRequestHeaders.Accept.Clear();
|
||||||
|
client.DefaultRequestHeaders.Add("User-Agent", "mRemote");
|
||||||
|
client.DefaultRequestHeaders.Add("OTP", CPSConnectionData.ssOTP);
|
||||||
|
|
||||||
|
var json = client.GetStringAsync(url).Result;
|
||||||
|
JsonNode? data = JsonSerializer.Deserialize<JsonNode>(json);
|
||||||
|
if (data == null)
|
||||||
|
return null;
|
||||||
|
JsonNode? element = data[0];
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
private static JsonNode? FetchDataAPIKeyAuth(int secretID)
|
||||||
|
{
|
||||||
|
string url = $"{CPSConnectionData.ssUrl}/api/passwords/{secretID}";
|
||||||
|
|
||||||
|
using HttpClient client = new HttpClient();
|
||||||
|
client.DefaultRequestHeaders.Accept.Clear();
|
||||||
|
client.DefaultRequestHeaders.Add("User-Agent", "mRemote");
|
||||||
|
client.DefaultRequestHeaders.Add("APIKey", CPSConnectionData.ssPassword);
|
||||||
|
client.DefaultRequestHeaders.Add("OTP", CPSConnectionData.ssOTP);
|
||||||
|
|
||||||
|
var json = client.GetStringAsync(url).Result;
|
||||||
|
JsonNode? data = JsonSerializer.Deserialize<JsonNode>(json);
|
||||||
|
if (data == null)
|
||||||
|
return null;
|
||||||
|
JsonNode? element = data[0];
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void FetchSecret(int secretID, out string secretUsername, out string secretPassword, out string secretDomain, out string privatekey)
|
||||||
|
{
|
||||||
|
// clear return variables
|
||||||
|
secretDomain = "";
|
||||||
|
secretUsername = "";
|
||||||
|
secretPassword = "";
|
||||||
|
privatekey = "";
|
||||||
|
string privatekeypassphrase = "";
|
||||||
|
JsonNode? element = null;
|
||||||
|
|
||||||
|
if (CPSConnectionData.ssSSO)
|
||||||
|
element = FetchDataWinAuth(secretID);
|
||||||
|
else
|
||||||
|
element = FetchDataAPIKeyAuth(secretID);
|
||||||
|
|
||||||
|
if (element == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var dom = element["Domain"];
|
||||||
|
if (dom != null) secretDomain = dom.ToString();
|
||||||
|
|
||||||
|
var user = element["UserName"];
|
||||||
|
if (user != null) secretUsername = user.ToString();
|
||||||
|
|
||||||
|
var pw = element["Password"];
|
||||||
|
if (pw != null) secretPassword = pw.ToString();
|
||||||
|
|
||||||
|
var privkey = element["GenericField1"];
|
||||||
|
if (privkey != null) privatekey = privkey.ToString();
|
||||||
|
|
||||||
|
var phrase = element["GenericField3"];
|
||||||
|
if (phrase != null) privatekeypassphrase = phrase.ToString();
|
||||||
|
|
||||||
|
// need to decode the private key?
|
||||||
|
if (!string.IsNullOrEmpty(privatekeypassphrase))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var key = DecodePrivateKey(privatekey, privatekeypassphrase);
|
||||||
|
privatekey = key;
|
||||||
|
}
|
||||||
|
catch(Exception)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// conversion to putty format necessary?
|
||||||
|
if (!string.IsNullOrEmpty(privatekey) && !privatekey.StartsWith("PuTTY-User-Key-File-2"))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
RSACryptoServiceProvider key = ImportPrivateKey(privatekey);
|
||||||
|
privatekey = PuttyKeyFileGenerator.ToPuttyPrivateKey(key);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region PUTTY KEY HANDLING
|
||||||
|
// decode rsa private key with encryption password
|
||||||
|
private static string DecodePrivateKey(string encryptedPrivateKey, string password)
|
||||||
|
{
|
||||||
|
TextReader textReader = new StringReader(encryptedPrivateKey);
|
||||||
|
PemReader pemReader = new PemReader(textReader, new PasswordFinder(password));
|
||||||
|
|
||||||
|
AsymmetricCipherKeyPair keyPair = (AsymmetricCipherKeyPair)pemReader.ReadObject();
|
||||||
|
|
||||||
|
TextWriter textWriter = new StringWriter();
|
||||||
|
var pemWriter = new PemWriter(textWriter);
|
||||||
|
pemWriter.WriteObject(keyPair.Private);
|
||||||
|
pemWriter.Writer.Flush();
|
||||||
|
|
||||||
|
return ""+textWriter.ToString();
|
||||||
|
}
|
||||||
|
private class PasswordFinder : IPasswordFinder
|
||||||
|
{
|
||||||
|
private string password;
|
||||||
|
|
||||||
|
public PasswordFinder(string password)
|
||||||
|
{
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public char[] GetPassword()
|
||||||
|
{
|
||||||
|
return password.ToCharArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// read private key pem string to rsacryptoserviceprovider
|
||||||
|
public static RSACryptoServiceProvider ImportPrivateKey(string pem)
|
||||||
|
{
|
||||||
|
PemReader pr = new PemReader(new StringReader(pem));
|
||||||
|
AsymmetricCipherKeyPair KeyPair = (AsymmetricCipherKeyPair)pr.ReadObject();
|
||||||
|
RSAParameters rsaParams = DotNetUtilities.ToRSAParameters((RsaPrivateCrtKeyParameters)KeyPair.Private);
|
||||||
|
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
|
||||||
|
rsa.ImportParameters(rsaParams);
|
||||||
|
return rsa;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
// input: must be the secret id to fetch
|
||||||
|
public static void FetchSecretFromServer(string secretID, out string username, out string password, out string domain, out string privatekey)
|
||||||
|
{
|
||||||
|
// get secret id
|
||||||
|
int sid = Int32.Parse(secretID);
|
||||||
|
|
||||||
|
// init connection credentials, display popup if necessary
|
||||||
|
CPSConnectionData.Init();
|
||||||
|
|
||||||
|
// get the secret
|
||||||
|
FetchSecret(sid, out username, out password, out domain, out privatekey);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,28 +7,28 @@ using SecretServerAuthentication.DSS;
|
|||||||
using SecretServerRestClient.DSS;
|
using SecretServerRestClient.DSS;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
namespace ExternalConnectors.DSS
|
namespace ExternalConnectors.DSS;
|
||||||
|
|
||||||
|
public class SecretServerInterface
|
||||||
{
|
{
|
||||||
public class SecretServerInterface
|
private static class SSConnectionData
|
||||||
{
|
{
|
||||||
private static class SSConnectionData
|
public static string ssUsername = "";
|
||||||
|
public static string ssPassword = "";
|
||||||
|
public static string ssUrl = "";
|
||||||
|
public static string ssOTP = "";
|
||||||
|
public static bool ssSSO = false;
|
||||||
|
public static bool initdone = false;
|
||||||
|
|
||||||
|
//token
|
||||||
|
public static string ssTokenBearer = "";
|
||||||
|
public static DateTime ssTokenExpiresOn = DateTime.UtcNow;
|
||||||
|
public static string ssTokenRefresh = "";
|
||||||
|
|
||||||
|
public static void Init()
|
||||||
{
|
{
|
||||||
public static string ssUsername = "";
|
if (initdone == true)
|
||||||
public static string ssPassword = "";
|
return;
|
||||||
public static string ssUrl = "";
|
|
||||||
public static string ssOTP = "";
|
|
||||||
public static bool ssSSO = false;
|
|
||||||
public static bool initdone = false;
|
|
||||||
|
|
||||||
//token
|
|
||||||
public static string ssTokenBearer = "";
|
|
||||||
public static DateTime ssTokenExpiresOn = DateTime.UtcNow;
|
|
||||||
public static string ssTokenRefresh = "";
|
|
||||||
|
|
||||||
public static void Init()
|
|
||||||
{
|
|
||||||
if (initdone == true)
|
|
||||||
return;
|
|
||||||
|
|
||||||
RegistryKey key = Registry.CurrentUser.CreateSubKey(@"SOFTWARE\mRemoteSSInterface");
|
RegistryKey key = Registry.CurrentUser.CreateSubKey(@"SOFTWARE\mRemoteSSInterface");
|
||||||
try
|
try
|
||||||
@@ -39,174 +39,174 @@ namespace ExternalConnectors.DSS
|
|||||||
f.tbUsername.Text = un ?? "";
|
f.tbUsername.Text = un ?? "";
|
||||||
f.tbPassword.Text = SSConnectionData.ssPassword; // in OTP refresh cases, this value might already be filled
|
f.tbPassword.Text = SSConnectionData.ssPassword; // in OTP refresh cases, this value might already be filled
|
||||||
|
|
||||||
string? url = key.GetValue("URL") as string;
|
string? url = key.GetValue("URL") as string;
|
||||||
if (url == null || !url.Contains("://"))
|
if (url == null || !url.Contains("://"))
|
||||||
url = "https://cred.domain.local/SecretServer";
|
url = "https://cred.domain.local/SecretServer";
|
||||||
f.tbSSURL.Text = url;
|
f.tbSSURL.Text = url;
|
||||||
|
|
||||||
var b = key.GetValue("SSO");
|
var b = key.GetValue("SSO");
|
||||||
if (b == null || (string)b != "True")
|
if (b == null || (string)b != "True")
|
||||||
ssSSO = false;
|
ssSSO = false;
|
||||||
else
|
else
|
||||||
ssSSO = true;
|
ssSSO = true;
|
||||||
f.cbUseSSO.Checked = ssSSO;
|
f.cbUseSSO.Checked = ssSSO;
|
||||||
|
|
||||||
// show dialog
|
// show dialog
|
||||||
while (true)
|
while (true)
|
||||||
|
{
|
||||||
|
_ = f.ShowDialog();
|
||||||
|
|
||||||
|
if (f.DialogResult != DialogResult.OK)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// store values to memory
|
||||||
|
ssUsername = f.tbUsername.Text;
|
||||||
|
ssPassword = f.tbPassword.Text;
|
||||||
|
ssUrl = f.tbSSURL.Text;
|
||||||
|
ssSSO = f.cbUseSSO.Checked;
|
||||||
|
ssOTP = f.tbOTP.Text;
|
||||||
|
// check connection first
|
||||||
|
try
|
||||||
{
|
{
|
||||||
_ = f.ShowDialog();
|
if (TestCredentials() == true)
|
||||||
|
|
||||||
if (f.DialogResult != DialogResult.OK)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// store values to memory
|
|
||||||
ssUsername = f.tbUsername.Text;
|
|
||||||
ssPassword = f.tbPassword.Text;
|
|
||||||
ssUrl = f.tbSSURL.Text;
|
|
||||||
ssSSO = f.cbUseSSO.Checked;
|
|
||||||
ssOTP = f.tbOTP.Text;
|
|
||||||
// check connection first
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
if (TestCredentials() == true)
|
initdone = true;
|
||||||
{
|
break;
|
||||||
initdone = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
MessageBox.Show("Test Credentials failed - please check your credentials");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
// write values to registry
|
MessageBox.Show("Test Credentials failed - please check your credentials");
|
||||||
key.SetValue("Username", ssUsername);
|
}
|
||||||
key.SetValue("URL", ssUrl);
|
|
||||||
key.SetValue("SSO", ssSSO);
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
key.Close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// write values to registry
|
||||||
|
key.SetValue("Username", ssUsername);
|
||||||
|
key.SetValue("URL", ssUrl);
|
||||||
|
key.SetValue("SSO", ssSSO);
|
||||||
}
|
}
|
||||||
}
|
catch (Exception)
|
||||||
|
{
|
||||||
private static bool TestCredentials()
|
throw;
|
||||||
{
|
}
|
||||||
if (SSConnectionData.ssSSO)
|
finally
|
||||||
|
{
|
||||||
|
key.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TestCredentials()
|
||||||
|
{
|
||||||
|
if (SSConnectionData.ssSSO)
|
||||||
|
{
|
||||||
|
// checking creds doesn't really make sense here, as we can't modify them anyway if something is wrong
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!String.IsNullOrEmpty(GetToken()))
|
||||||
{
|
{
|
||||||
// checking creds doesn't really make sense here, as we can't modify them anyway if something is wrong
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
return false;
|
||||||
if (!String.IsNullOrEmpty(GetToken()))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static SecretsServiceClient ConstructSecretsServiceClient()
|
private static SecretsServiceClient ConstructSecretsServiceClient()
|
||||||
|
{
|
||||||
|
string baseURL = SSConnectionData.ssUrl;
|
||||||
|
if (SSConnectionData.ssSSO)
|
||||||
{
|
{
|
||||||
string baseURL = SSConnectionData.ssUrl;
|
// REQUIRES IIS CONFIG! https://docs.thycotic.com/ss/11.0.0/api-scripting/webservice-iwa-powershell
|
||||||
if (SSConnectionData.ssSSO)
|
var handler = new HttpClientHandler() { UseDefaultCredentials = true };
|
||||||
|
var httpClient = new HttpClient(handler);
|
||||||
{
|
{
|
||||||
// REQUIRES IIS CONFIG! https://docs.thycotic.com/ss/11.0.0/api-scripting/webservice-iwa-powershell
|
// Call REST API:
|
||||||
var handler = new HttpClientHandler() { UseDefaultCredentials = true };
|
return new SecretsServiceClient($"{baseURL}/winauthwebservices/api", httpClient);
|
||||||
var httpClient = new HttpClient(handler);
|
|
||||||
{
|
|
||||||
// Call REST API:
|
|
||||||
return new SecretsServiceClient($"{baseURL}/winauthwebservices/api", httpClient);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
var httpClient = new HttpClient();
|
|
||||||
{
|
|
||||||
|
|
||||||
var token = GetToken();
|
|
||||||
// Set credentials (token):
|
|
||||||
httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
|
||||||
|
|
||||||
// Call REST API:
|
|
||||||
return new SecretsServiceClient($"{baseURL}/api", httpClient);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
private static void FetchSecret(int secretID, out string secretUsername, out string secretPassword, out string secretDomain, out string privatekey)
|
else
|
||||||
{
|
{
|
||||||
var client = ConstructSecretsServiceClient();
|
var httpClient = new HttpClient();
|
||||||
SecretModel secret = client.GetSecretAsync(false, true, secretID, null).Result;
|
|
||||||
|
|
||||||
// clear return variables
|
|
||||||
secretDomain = "";
|
|
||||||
secretUsername = "";
|
|
||||||
secretPassword = "";
|
|
||||||
privatekey = "";
|
|
||||||
string privatekeypassphrase = "";
|
|
||||||
|
|
||||||
// parse data and extract what we need
|
|
||||||
foreach (var item in secret.Items)
|
|
||||||
{
|
{
|
||||||
if (item.FieldName.ToLower().Equals("domain"))
|
|
||||||
secretDomain = item.ItemValue;
|
|
||||||
else if (item.FieldName.ToLower().Equals("username"))
|
|
||||||
secretUsername = item.ItemValue;
|
|
||||||
else if (item.FieldName.ToLower().Equals("password"))
|
|
||||||
secretPassword = item.ItemValue;
|
|
||||||
else if (item.FieldName.ToLower().Equals("private key"))
|
|
||||||
{
|
|
||||||
client.ReadResponseNoJSONConvert = true;
|
|
||||||
privatekey = client.GetFieldAsync(false, false, secretID, "private-key").Result;
|
|
||||||
client.ReadResponseNoJSONConvert = false;
|
|
||||||
}
|
|
||||||
else if (item.FieldName.ToLower().Equals("private key passphrase"))
|
|
||||||
privatekeypassphrase = item.ItemValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// need to decode the private key?
|
var token = GetToken();
|
||||||
if (!string.IsNullOrEmpty(privatekeypassphrase))
|
// Set credentials (token):
|
||||||
{
|
httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
||||||
try
|
|
||||||
{
|
|
||||||
var key = DecodePrivateKey(privatekey, privatekeypassphrase);
|
|
||||||
privatekey = key;
|
|
||||||
}
|
|
||||||
catch(Exception)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
// Call REST API:
|
||||||
}
|
return new SecretsServiceClient($"{baseURL}/api", httpClient);
|
||||||
|
|
||||||
// conversion to putty format necessary?
|
|
||||||
if (!string.IsNullOrEmpty(privatekey) && !privatekey.StartsWith("PuTTY-User-Key-File-2"))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
RSACryptoServiceProvider key = ImportPrivateKey(privatekey);
|
|
||||||
privatekey = PuttyKeyFileGenerator.ToPuttyPrivateKey(key);
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
private static void FetchSecret(int secretID, out string secretUsername, out string secretPassword, out string secretDomain, out string privatekey)
|
||||||
|
{
|
||||||
|
var client = ConstructSecretsServiceClient();
|
||||||
|
SecretModel secret = client.GetSecretAsync(false, true, secretID, null).Result;
|
||||||
|
|
||||||
|
// clear return variables
|
||||||
|
secretDomain = "";
|
||||||
|
secretUsername = "";
|
||||||
|
secretPassword = "";
|
||||||
|
privatekey = "";
|
||||||
|
string privatekeypassphrase = "";
|
||||||
|
|
||||||
|
// parse data and extract what we need
|
||||||
|
foreach (var item in secret.Items)
|
||||||
|
{
|
||||||
|
if (item.FieldName.ToLower().Equals("domain"))
|
||||||
|
secretDomain = item.ItemValue;
|
||||||
|
else if (item.FieldName.ToLower().Equals("username"))
|
||||||
|
secretUsername = item.ItemValue;
|
||||||
|
else if (item.FieldName.ToLower().Equals("password"))
|
||||||
|
secretPassword = item.ItemValue;
|
||||||
|
else if (item.FieldName.ToLower().Equals("private key"))
|
||||||
|
{
|
||||||
|
client.ReadResponseNoJSONConvert = true;
|
||||||
|
privatekey = client.GetFieldAsync(false, false, secretID, "private-key").Result;
|
||||||
|
client.ReadResponseNoJSONConvert = false;
|
||||||
|
}
|
||||||
|
else if (item.FieldName.ToLower().Equals("private key passphrase"))
|
||||||
|
privatekeypassphrase = item.ItemValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// need to decode the private key?
|
||||||
|
if (!string.IsNullOrEmpty(privatekeypassphrase))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var key = DecodePrivateKey(privatekey, privatekeypassphrase);
|
||||||
|
privatekey = key;
|
||||||
|
}
|
||||||
|
catch(Exception)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// conversion to putty format necessary?
|
||||||
|
if (!string.IsNullOrEmpty(privatekey) && !privatekey.StartsWith("PuTTY-User-Key-File-2"))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
RSACryptoServiceProvider key = ImportPrivateKey(privatekey);
|
||||||
|
privatekey = PuttyKeyFileGenerator.ToPuttyPrivateKey(key);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#region PUTTY KEY HANDLING
|
#region PUTTY KEY HANDLING
|
||||||
// decode rsa private key with encryption password
|
// decode rsa private key with encryption password
|
||||||
private static string DecodePrivateKey(string encryptedPrivateKey, string password)
|
private static string DecodePrivateKey(string encryptedPrivateKey, string password)
|
||||||
@@ -214,31 +214,31 @@ namespace ExternalConnectors.DSS
|
|||||||
TextReader textReader = new StringReader(encryptedPrivateKey);
|
TextReader textReader = new StringReader(encryptedPrivateKey);
|
||||||
PemReader pemReader = new(textReader, new PasswordFinder(password));
|
PemReader pemReader = new(textReader, new PasswordFinder(password));
|
||||||
|
|
||||||
AsymmetricCipherKeyPair keyPair = (AsymmetricCipherKeyPair)pemReader.ReadObject();
|
AsymmetricCipherKeyPair keyPair = (AsymmetricCipherKeyPair)pemReader.ReadObject();
|
||||||
|
|
||||||
TextWriter textWriter = new StringWriter();
|
TextWriter textWriter = new StringWriter();
|
||||||
var pemWriter = new PemWriter(textWriter);
|
var pemWriter = new PemWriter(textWriter);
|
||||||
pemWriter.WriteObject(keyPair.Private);
|
pemWriter.WriteObject(keyPair.Private);
|
||||||
pemWriter.Writer.Flush();
|
pemWriter.Writer.Flush();
|
||||||
|
|
||||||
return ""+textWriter.ToString();
|
return ""+textWriter.ToString();
|
||||||
}
|
}
|
||||||
private class PasswordFinder : IPasswordFinder
|
private class PasswordFinder : IPasswordFinder
|
||||||
|
{
|
||||||
|
private string password;
|
||||||
|
|
||||||
|
public PasswordFinder(string password)
|
||||||
{
|
{
|
||||||
private string password;
|
this.password = password;
|
||||||
|
|
||||||
public PasswordFinder(string password)
|
|
||||||
{
|
|
||||||
this.password = password;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public char[] GetPassword()
|
|
||||||
{
|
|
||||||
return password.ToCharArray();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public char[] GetPassword()
|
||||||
|
{
|
||||||
|
return password.ToCharArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// read private key pem string to rsacryptoserviceprovider
|
// read private key pem string to rsacryptoserviceprovider
|
||||||
public static RSACryptoServiceProvider ImportPrivateKey(string pem)
|
public static RSACryptoServiceProvider ImportPrivateKey(string pem)
|
||||||
{
|
{
|
||||||
@@ -252,91 +252,90 @@ namespace ExternalConnectors.DSS
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
#region TOKEN
|
#region TOKEN
|
||||||
private static string GetToken()
|
private static string GetToken()
|
||||||
|
{
|
||||||
|
// if there is no token, fetch a fresh one
|
||||||
|
if (String.IsNullOrEmpty(SSConnectionData.ssTokenBearer))
|
||||||
{
|
{
|
||||||
// if there is no token, fetch a fresh one
|
return GetTokenFresh();
|
||||||
if (String.IsNullOrEmpty(SSConnectionData.ssTokenBearer))
|
}
|
||||||
|
// if there is a token, check if it is valid
|
||||||
|
if (SSConnectionData.ssTokenExpiresOn >= DateTime.UtcNow)
|
||||||
|
{
|
||||||
|
return SSConnectionData.ssTokenBearer;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// try using refresh token
|
||||||
|
using (var httpClient = new HttpClient())
|
||||||
{
|
{
|
||||||
return GetTokenFresh();
|
var tokenClient = new OAuth2ServiceClient(SSConnectionData.ssUrl, httpClient);
|
||||||
}
|
TokenResponse token = new();
|
||||||
// if there is a token, check if it is valid
|
try
|
||||||
if (SSConnectionData.ssTokenExpiresOn >= DateTime.UtcNow)
|
|
||||||
{
|
|
||||||
return SSConnectionData.ssTokenBearer;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// try using refresh token
|
|
||||||
using (var httpClient = new HttpClient())
|
|
||||||
{
|
{
|
||||||
var tokenClient = new OAuth2ServiceClient(SSConnectionData.ssUrl, httpClient);
|
token = tokenClient.AuthorizeAsync(Grant_type.Refresh_token, null, null, SSConnectionData.ssTokenRefresh, null).Result;
|
||||||
TokenResponse token = new();
|
var tokenResult = token.Access_token;
|
||||||
try
|
|
||||||
{
|
|
||||||
token = tokenClient.AuthorizeAsync(Grant_type.Refresh_token, null, null, SSConnectionData.ssTokenRefresh, null).Result;
|
|
||||||
var tokenResult = token.Access_token;
|
|
||||||
|
|
||||||
SSConnectionData.ssTokenBearer = tokenResult;
|
SSConnectionData.ssTokenBearer = tokenResult;
|
||||||
SSConnectionData.ssTokenRefresh = token.Refresh_token;
|
SSConnectionData.ssTokenRefresh = token.Refresh_token;
|
||||||
SSConnectionData.ssTokenExpiresOn = token.Expires_on;
|
SSConnectionData.ssTokenExpiresOn = token.Expires_on;
|
||||||
return tokenResult;
|
return tokenResult;
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// refresh token failed. clean memory and start fresh
|
||||||
|
SSConnectionData.ssTokenBearer = "";
|
||||||
|
SSConnectionData.ssTokenRefresh = "";
|
||||||
|
SSConnectionData.ssTokenExpiresOn = DateTime.Now;
|
||||||
|
// if OTP is required we need to ask user for a new OTP
|
||||||
|
if (!String.IsNullOrEmpty(SSConnectionData.ssOTP))
|
||||||
{
|
{
|
||||||
// refresh token failed. clean memory and start fresh
|
SSConnectionData.initdone = false;
|
||||||
SSConnectionData.ssTokenBearer = "";
|
// the call below executes a connection test, which fetches a valid token
|
||||||
SSConnectionData.ssTokenRefresh = "";
|
SSConnectionData.Init();
|
||||||
SSConnectionData.ssTokenExpiresOn = DateTime.Now;
|
// we now have a fresh token in memory. return it to caller
|
||||||
// if OTP is required we need to ask user for a new OTP
|
return SSConnectionData.ssTokenBearer;
|
||||||
if (!String.IsNullOrEmpty(SSConnectionData.ssOTP))
|
}
|
||||||
{
|
else
|
||||||
SSConnectionData.initdone = false;
|
{
|
||||||
// the call below executes a connection test, which fetches a valid token
|
// no user interaction required. get a fresh token and return it to caller
|
||||||
SSConnectionData.Init();
|
return GetTokenFresh();
|
||||||
// we now have a fresh token in memory. return it to caller
|
|
||||||
return SSConnectionData.ssTokenBearer;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// no user interaction required. get a fresh token and return it to caller
|
|
||||||
return GetTokenFresh();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
static string GetTokenFresh()
|
}
|
||||||
|
static string GetTokenFresh()
|
||||||
|
{
|
||||||
|
using (var httpClient = new HttpClient())
|
||||||
{
|
{
|
||||||
using (var httpClient = new HttpClient())
|
// Authenticate:
|
||||||
{
|
var tokenClient = new OAuth2ServiceClient(SSConnectionData.ssUrl, httpClient);
|
||||||
// Authenticate:
|
// call below will throw an exception if the creds are invalid
|
||||||
var tokenClient = new OAuth2ServiceClient(SSConnectionData.ssUrl, httpClient);
|
var token = tokenClient.AuthorizeAsync(Grant_type.Password, SSConnectionData.ssUsername, SSConnectionData.ssPassword, null, SSConnectionData.ssOTP).Result;
|
||||||
// call below will throw an exception if the creds are invalid
|
// here we can be sure the creds are ok - return success state
|
||||||
var token = tokenClient.AuthorizeAsync(Grant_type.Password, SSConnectionData.ssUsername, SSConnectionData.ssPassword, null, SSConnectionData.ssOTP).Result;
|
var tokenResult = token.Access_token;
|
||||||
// here we can be sure the creds are ok - return success state
|
|
||||||
var tokenResult = token.Access_token;
|
|
||||||
|
|
||||||
SSConnectionData.ssTokenBearer = tokenResult;
|
SSConnectionData.ssTokenBearer = tokenResult;
|
||||||
SSConnectionData.ssTokenRefresh = token.Refresh_token;
|
SSConnectionData.ssTokenRefresh = token.Refresh_token;
|
||||||
SSConnectionData.ssTokenExpiresOn = token.Expires_on;
|
SSConnectionData.ssTokenExpiresOn = token.Expires_on;
|
||||||
return tokenResult;
|
return tokenResult;
|
||||||
}
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
// input must be the secret id to fetch
|
|
||||||
public static void FetchSecretFromServer(string input, out string username, out string password, out string domain, out string privatekey)
|
|
||||||
{
|
|
||||||
// get secret id
|
|
||||||
int secretID = Int32.Parse(input);
|
|
||||||
|
|
||||||
// init connection credentials, display popup if necessary
|
|
||||||
SSConnectionData.Init();
|
|
||||||
|
|
||||||
// get the secret
|
|
||||||
FetchSecret(secretID, out username, out password, out domain, out privatekey);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
// input must be the secret id to fetch
|
||||||
|
public static void FetchSecretFromServer(string input, out string username, out string password, out string domain, out string privatekey)
|
||||||
|
{
|
||||||
|
// get secret id
|
||||||
|
int secretID = Int32.Parse(input);
|
||||||
|
|
||||||
|
// init connection credentials, display popup if necessary
|
||||||
|
SSConnectionData.Init();
|
||||||
|
|
||||||
|
// get the secret
|
||||||
|
FetchSecret(secretID, out username, out password, out domain, out privatekey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,8 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AWSSDK.Core" Version="3.7.303.25" />
|
<PackageReference Include="AWSSDK.Core" Version="3.7.303.26" />
|
||||||
<PackageReference Include="AWSSDK.EC2" Version="3.7.326" />
|
<PackageReference Include="AWSSDK.EC2" Version="3.7.326.1" />
|
||||||
<PackageReference Include="BouncyCastle.Cryptography" Version="2.3.0" />
|
<PackageReference Include="BouncyCastle.Cryptography" Version="2.3.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -25,6 +25,9 @@
|
|||||||
<Compile Update="AWS\AWSConnectionForm.cs">
|
<Compile Update="AWS\AWSConnectionForm.cs">
|
||||||
<SubType>Form</SubType>
|
<SubType>Form</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Update="CPS\CPSConnectionForm.cs">
|
||||||
|
<SubType>Form</SubType>
|
||||||
|
</Compile>
|
||||||
<Compile Update="DSS\SSConnectionForm.cs">
|
<Compile Update="DSS\SSConnectionForm.cs">
|
||||||
<SubType>Form</SubType>
|
<SubType>Form</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ namespace mRemoteNG.Connection
|
|||||||
None = 0,
|
None = 0,
|
||||||
|
|
||||||
[LocalizedAttributes.LocalizedDescription(nameof(Language.ECPDelineaSecretServer))]
|
[LocalizedAttributes.LocalizedDescription(nameof(Language.ECPDelineaSecretServer))]
|
||||||
DelineaSecretServer = 1
|
DelineaSecretServer = 1,
|
||||||
|
|
||||||
|
[LocalizedAttributes.LocalizedDescription(nameof(Language.ECPClickstudiosPasswordstate))]
|
||||||
|
ClickstudiosPasswordState = 2
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ using System.Linq;
|
|||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
using static System.Windows.Forms.VisualStyles.VisualStyleElement.StartPanel;
|
||||||
|
|
||||||
// ReSharper disable ArrangeAccessorOwnerBody
|
// ReSharper disable ArrangeAccessorOwnerBody
|
||||||
|
|
||||||
@@ -127,6 +128,28 @@ namespace mRemoteNG.Connection.Protocol
|
|||||||
Event_ErrorOccured(this, "Secret Server Interface Error: " + ex.Message, 0);
|
Event_ErrorOccured(this, "Secret Server Interface Error: " + ex.Message, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (InterfaceControl.Info.ExternalCredentialProvider == ExternalCredentialProvider.ClickstudiosPasswordState)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ExternalConnectors.CPS.PasswordstateInterface.FetchSecretFromServer($"{UserViaAPI}", out username, out password, out domain, out privatekey);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(privatekey))
|
||||||
|
{
|
||||||
|
optionalTemporaryPrivateKeyPath = Path.GetTempFileName();
|
||||||
|
File.WriteAllText(optionalTemporaryPrivateKeyPath, privatekey);
|
||||||
|
FileInfo fileInfo = new(optionalTemporaryPrivateKeyPath)
|
||||||
|
{
|
||||||
|
Attributes = FileAttributes.Temporary
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Event_ErrorOccured(this, "Passwordstate Interface Error: " + ex.Message, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(username))
|
if (string.IsNullOrEmpty(username))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ using MSTSCLib;
|
|||||||
using mRemoteNG.Resources.Language;
|
using mRemoteNG.Resources.Language;
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
using FileDialog = Microsoft.Win32.FileDialog;
|
using FileDialog = Microsoft.Win32.FileDialog;
|
||||||
|
using static System.Windows.Forms.VisualStyles.VisualStyleElement.StartPanel;
|
||||||
|
using System.DirectoryServices.ActiveDirectory;
|
||||||
|
|
||||||
namespace mRemoteNG.Connection.Protocol.RDP
|
namespace mRemoteNG.Connection.Protocol.RDP
|
||||||
{
|
{
|
||||||
@@ -450,8 +452,20 @@ namespace mRemoteNG.Connection.Protocol.RDP
|
|||||||
{
|
{
|
||||||
Event_ErrorOccured(this, "Secret Server Interface Error: " + ex.Message, 0);
|
Event_ErrorOccured(this, "Secret Server Interface Error: " + ex.Message, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
else if (InterfaceControl.Info.ExternalCredentialProvider == ExternalCredentialProvider.ClickstudiosPasswordState)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string RDGUserViaAPI = InterfaceControl.Info.RDGatewayUserViaAPI;
|
||||||
|
ExternalConnectors.CPS.PasswordstateInterface.FetchSecretFromServer($"{RDGUserViaAPI}", out gwu, out gwp, out gwd, out pkey);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Event_ErrorOccured(this, "Passwordstate Interface Error: " + ex.Message, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (connectionInfo.RDGatewayUseConnectionCredentials != RDGatewayUseConnectionCredentials.AccessToken)
|
if (connectionInfo.RDGatewayUseConnectionCredentials != RDGatewayUseConnectionCredentials.AccessToken)
|
||||||
{
|
{
|
||||||
@@ -538,7 +552,17 @@ namespace mRemoteNG.Connection.Protocol.RDP
|
|||||||
{
|
{
|
||||||
Event_ErrorOccured(this, "Secret Server Interface Error: " + ex.Message, 0);
|
Event_ErrorOccured(this, "Secret Server Interface Error: " + ex.Message, 0);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else if (InterfaceControl.Info.ExternalCredentialProvider == ExternalCredentialProvider.ClickstudiosPasswordState)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ExternalConnectors.CPS.PasswordstateInterface.FetchSecretFromServer($"{userViaApi}", out userName, out password, out domain, out pkey);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Event_ErrorOccured(this, "Passwordstate Interface Error: " + ex.Message, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(userName))
|
if (string.IsNullOrEmpty(userName))
|
||||||
|
|||||||
9
mRemoteNG/Language/Language.Designer.cs
generated
9
mRemoteNG/Language/Language.Designer.cs
generated
@@ -1807,6 +1807,15 @@ namespace mRemoteNG.Resources.Language {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Clickstudios Passwordstate.
|
||||||
|
/// </summary>
|
||||||
|
internal static string ECPClickstudiosPasswordstate {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("ECPClickstudiosPasswordstate", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Delinea Secret Server.
|
/// Looks up a localized string similar to Delinea Secret Server.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -2289,7 +2289,10 @@ Nightly Channel includes Alphas, Betas & Release Candidates.</value>
|
|||||||
<data name="ECPDelineaSecretServer" xml:space="preserve">
|
<data name="ECPDelineaSecretServer" xml:space="preserve">
|
||||||
<value>Delinea Secret Server</value>
|
<value>Delinea Secret Server</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ECPNone" xml:space="preserve">
|
<data name="ECPClickstudiosPasswordstate" xml:space="preserve">
|
||||||
|
<value>Clickstudios Passwordstate</value>
|
||||||
|
</data>
|
||||||
|
<data name="ECPNone" xml:space="preserve">
|
||||||
<value>None</value>
|
<value>None</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="EAPAmazonWebServices" xml:space="preserve">
|
<data name="EAPAmazonWebServices" xml:space="preserve">
|
||||||
|
|||||||
@@ -238,7 +238,8 @@ namespace mRemoteNG.UI.Controls.ConnectionInfoPropertyGrid
|
|||||||
{
|
{
|
||||||
strHide.Add(nameof(AbstractConnectionRecord.UserViaAPI));
|
strHide.Add(nameof(AbstractConnectionRecord.UserViaAPI));
|
||||||
}
|
}
|
||||||
else if (SelectedConnectionInfo.ExternalCredentialProvider == ExternalCredentialProvider.DelineaSecretServer)
|
else if (SelectedConnectionInfo.ExternalCredentialProvider == ExternalCredentialProvider.DelineaSecretServer
|
||||||
|
|| SelectedConnectionInfo.ExternalCredentialProvider == ExternalCredentialProvider.ClickstudiosPasswordState)
|
||||||
{
|
{
|
||||||
strHide.Add(nameof(AbstractConnectionRecord.Username));
|
strHide.Add(nameof(AbstractConnectionRecord.Username));
|
||||||
strHide.Add(nameof(AbstractConnectionRecord.Password));
|
strHide.Add(nameof(AbstractConnectionRecord.Password));
|
||||||
|
|||||||
@@ -1,21 +1,44 @@
|
|||||||
*************
|
**************************
|
||||||
Credential Vault Connector
|
Credential Vault Connector
|
||||||
*************
|
**************************
|
||||||
|
|
||||||
.. warning::
|
mRemote supports fetching credentials from external credential vaults. This allows providing credentials to the connection without storing sensitive information in the config file, which has numerous benefits (security, auditing, rotating passwords, etc).
|
||||||
|
Two password vaults are currently supported:
|
||||||
|
|
||||||
This feature is currently only developed for Thycotic Secret Server (on-premise installations). It is implemented for RDP and SSH connections.
|
- Delinea Secret Server
|
||||||
|
- Clickstudios Passwordstate
|
||||||
|
|
||||||
mRemote supports fetching credentials from external credential vaults. This allows providing credentials to the connection without storing these to disk, which has numerous benefits (security, auditing, rotating passwords, etc).
|
The feature is implemented for RDP, RDP Gateway and SSH connections.
|
||||||
|
|
||||||
Instead of specifying username/password/domain directly in mRemote, leave these fields empty and just set the secret id:
|
Before initiating a connection mRemote will access your Password Vault API and fetch the secret. For this to work the API endpoint URL and access credentials need to be specified. A popup will show up if this information has not yet been set.
|
||||||
|
|
||||||
.. figure:: /images/credvault01.png
|
|
||||||
|
|
||||||
The secret id is the unique identifier of your secret, you can find it in the URL in your thycotic interface.
|
|
||||||
e.g. https://cred.domain.local/SecretServer/app/#/secret/3318/general -> the secret id is 3318
|
|
||||||
|
|
||||||
Before initiating the connection mRemote will access your Secret Server API URL and fetch the data. For this to work the API endpoint URL and access credentials need to be specified. A popup will show up if this information has not yet been set.
|
|
||||||
|
|
||||||
.. figure:: /images/credvault02.png
|
.. figure:: /images/credvault02.png
|
||||||
|
|
||||||
|
|
||||||
|
Instead of setting username/password/domain directly in mRemote, leave these fields empty and specify the secret id instead:
|
||||||
|
|
||||||
|
.. figure:: /images/credvault01.png
|
||||||
|
|
||||||
|
The secret id is the unique identifier of your secret.
|
||||||
|
|
||||||
|
|
||||||
|
Delinea Secret Server
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
The secret ID can be found in the url of your secret: https://cred.domain.local/SecretServer/app/#/secret/3318/general -> the secret id is 3318
|
||||||
|
|
||||||
|
Authentication works with WinAuth/SSO (OnPremise) and Username/Password (OnPremise, Cloud). MFA via OTP is supported.
|
||||||
|
|
||||||
|
|
||||||
|
Clickstudios PasswordState
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
The secred ID can be found in the UI after enabling "toggle visibility of web API IDs" in the "List Administrator Actions" dropdown
|
||||||
|
|
||||||
|
.. figure:: /images/credvault03.png
|
||||||
|
|
||||||
|
Authentication works with WinAuth/SSO and list-based API-Keys. MFA via OTP is supported.
|
||||||
|
|
||||||
|
- There is currently no support for token authentication, so if your API has MFA enabled, you need to specify a fresh OTP code quite frequently
|
||||||
|
- If you are using list-based API keys to access the vault, only one API key can currently be specified in the connector configuration
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 4.6 KiB |
BIN
mRemoteNGDocumentation/images/credvault03.png
Normal file
BIN
mRemoteNGDocumentation/images/credvault03.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
Reference in New Issue
Block a user