Skip to content

Implemented /login/sso redirect and fixed some minor issues #26

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion Rally.RestApi.Test/RallyRestApiTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Rally.RestApi.Auth;
using Rally.RestApi.Response;
using Rally.RestApi.Test.Properties;
using Rally.RestApi.Connection;
Expand Down Expand Up @@ -397,14 +399,22 @@ public void TestIsNotWsapi2()
Assert.IsFalse(restApi.IsWsapi2);
}

[TestMethod]
public void TestIdpLoginEndpointRedirect()
{
LoginDetails login = new LoginDetails(new ApiConsoleAuthManager());
string redirectUrl = login.RedirectIfIdpPointsAtLoginSso("your-idp-url&TargetResource=https://rally1.rallydev.com/login/sso");
Assert.AreEqual(redirectUrl, "your-idp-url&TargetResource=https://rally1.rallydev.com/slm/empty.sp");
}

private static void VerifyAttributes(QueryResult result, bool forWsapi2)
{
var list = (IEnumerable<object>)result.Results;
IEnumerable<string> names = from DynamicJsonObject i in list.Cast<DynamicJsonObject>()
select i["Name"] as string;
string[] expectedNames;
if (forWsapi2)
expectedNames = new string[] { "App Id", "Creation Date", "VersionId", "Object ID", "Name", "Project", "User", "Value", "Workspace" };
expectedNames = new string[] { "App Id", "Creation Date", "VersionId", "Object ID", "Name", "Project", "User", "Value", "Workspace", "ObjectUUID", "Type" };
else
expectedNames = new string[] { "App Id", "Creation Date", "Object ID", "Name", "Project", "User", "Value", "Workspace" };

Expand Down
38 changes: 16 additions & 22 deletions Rally.RestApi.UiForWpf/LoginWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,13 @@
using Rally.RestApi.Auth;
using System;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Rally.RestApi.Auth;
using Rally.RestApi.Connection;

namespace Rally.RestApi.UiForWpf
Expand All @@ -30,7 +23,7 @@ private enum TabType
{
Credentials,
Rally,
Proxy,
Proxy
}
#endregion

Expand All @@ -45,7 +38,7 @@ private enum EditorControlType
TrustAllCertificates,
ProxyServer,
ProxyUsername,
ProxyPassword,
ProxyPassword
}
#endregion

Expand All @@ -72,15 +65,16 @@ public LoginWindow()
{
InitializeComponent();

RestApiAuthMgrWpf.AllowIdpBasedSso = true;
headerLabel.Content = ApiAuthManager.LoginWindowHeaderLabelText;
controls = new Dictionary<EditorControlType, Control>();
controlReadOnlyLabels = new Dictionary<Control, Label>();
tabControls = new Dictionary<TabType, Control>();
controlRowElements = new Dictionary<EditorControlType, RowDefinition>();

connectionTypes = new Dictionary<ConnectionType, string>();
connectionTypes.Add(ConnectionType.BasicAuth, "Basic Authentication (will try Rally SSO if it fails)");
connectionTypes.Add(ConnectionType.SpBasedSso, "Rally based SSO Authentication");
connectionTypes.Add(ConnectionType.BasicAuth, "Basic Authentication (will try CA Agile Central SSO if it fails)");
connectionTypes.Add(ConnectionType.SpBasedSso, "CA Agile Central based SSO Authentication");
connectionTypes.Add(ConnectionType.IdpBasedSso, "IDP Based SSO Authentication");
}
#endregion
Expand Down Expand Up @@ -148,9 +142,9 @@ internal void BuildLayout(RestApiAuthMgrWpf authMgr)
inputRow.Height = new GridLength(tabControl.Height + 20, GridUnitType.Pixel);
inputRow.MinHeight = inputRow.Height.Value;

this.Height = inputRow.Height.Value + (28 * 2) + 100;
this.MinHeight = this.Height;
this.MaxHeight = this.Height;
Height = inputRow.Height.Value + (28 * 2) + 100;
MinHeight = Height;
MaxHeight = Height;

SetDefaultValues();
ConnectionTypeChanged(GetEditor(EditorControlType.ConnectionType), null);
Expand Down Expand Up @@ -543,13 +537,15 @@ private string GetEditorValue(EditorControlType controlType)
return null;

TextBox textBox = control as TextBox;
if (textBox != null && controlType == EditorControlType.IdpServer)
return AuthMgr.LoginDetails.RedirectIfIdpPointsAtLoginSso(textBox.Text);

if (textBox != null)
return textBox.Text;

PasswordBox passwordBox = control as PasswordBox;
if (passwordBox != null)
return passwordBox.Password;

return null;
}
#endregion
Expand Down Expand Up @@ -586,8 +582,6 @@ private void AddButtons()
AddColumnDefinition(buttonGrid, 70);
AddColumnDefinition(buttonGrid);



loginButton = GetButton();
loginButton.IsDefault = true;
loginButton.Content = ApiAuthManager.LoginWindowLoginText;
Expand Down Expand Up @@ -679,7 +673,7 @@ private void AddColumnDefinition(Grid grid, int pixels = Int32.MaxValue)
void loginButton_Click(object sender, RoutedEventArgs e)
{
string errorMessage;
ShowMessage("Logging into Rally");
ShowMessage("Logging into CA Agile Central");

AuthMgr.LoginDetails.Username = GetEditorValue(EditorControlType.Username);
AuthMgr.LoginDetails.SetPassword(GetEditorValue(EditorControlType.Password));
Expand Down Expand Up @@ -737,7 +731,7 @@ private void ShowMessage(string message = "")
#endregion

#region OnClosing
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
protected override void OnClosing(CancelEventArgs e)
{
AuthMgr.LoginWindowSsoAuthenticationComplete = null;
base.OnClosing(e);
Expand Down
2 changes: 1 addition & 1 deletion Rally.RestApi.UiForWpf/SsoWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
x:ClassModifier="internal"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Topmost="True"
Title="Single Sign On for Rally" Height="700" Width="800">
Title="Single Sign On for CA Agile Central" Height="700" Width="800">
<Grid>
<WebBrowser x:Name="browser" Margin="2 2 2 2" />
</Grid>
Expand Down
15 changes: 15 additions & 0 deletions Rally.RestApi.UiForWpf/SsoWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public SsoWindow()
{
InitializeComponent();
browser.LoadCompleted += browser_LoadCompleted;
browser.Navigated += (a, b) => HideScriptErrors(browser, true);
}
#endregion

Expand Down Expand Up @@ -181,5 +182,19 @@ protected override void OnClosed(EventArgs e)
base.OnClosed(e);
}
#endregion

private void HideScriptErrors(WebBrowser browser, bool hide)
{
var fiComWebBrowser = typeof(WebBrowser).GetField("_axIWebBrowser2", BindingFlags.Instance | BindingFlags.NonPublic);
if (fiComWebBrowser == null) return;
var objComWebBrowser = fiComWebBrowser.GetValue(browser);
if (objComWebBrowser == null)
{
browser.Navigated += (o, s) => HideScriptErrors(browser, hide);
return;
}
objComWebBrowser.GetType()
.InvokeMember("Silent", BindingFlags.SetProperty, null, objComWebBrowser, new object[] { hide });
}
}
}
20 changes: 10 additions & 10 deletions Rally.RestApi/Auth/ApiAuthManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -293,11 +293,11 @@ public static void Configure(string loginWindowTitle = null,
{
LoginWindowTitle = loginWindowTitle;
if (String.IsNullOrWhiteSpace(LoginWindowTitle))
LoginWindowTitle = "Login to Rally";
LoginWindowTitle = "Login to CA Agile Central";

LoginWindowHeaderLabelText = loginWindowHeaderLabelText;
if (String.IsNullOrWhiteSpace(LoginWindowHeaderLabelText))
LoginWindowHeaderLabelText = "Login to Rally";
LoginWindowHeaderLabelText = "Login to CA Agile Central";

LoginWindowDefaultServer = loginWindowDefaultServer;
if (LoginWindowDefaultServer == null)
Expand Down Expand Up @@ -325,7 +325,7 @@ public static void Configure(string loginWindowTitle = null,

LoginWindowRallyServerTabText = loginWindowServerTabText;
if (String.IsNullOrWhiteSpace(LoginWindowRallyServerTabText))
LoginWindowRallyServerTabText = "Rally";
LoginWindowRallyServerTabText = "CA Agile Central";

LoginWindowServerLabelText = loginWindowServerLabelText;
if (String.IsNullOrWhiteSpace(LoginWindowServerLabelText))
Expand Down Expand Up @@ -391,11 +391,11 @@ public static void Configure(string loginWindowTitle = null,

LoginFailureServerEmpty = loginFailureServerEmpty;
if (String.IsNullOrWhiteSpace(LoginFailureServerEmpty))
LoginFailureServerEmpty = "Rally Server is a required field.";
LoginFailureServerEmpty = "CA Agile Central Server is a required field.";

LoginFailureBadConnection = loginFailureBadConnection;
if (String.IsNullOrWhiteSpace(LoginFailureBadConnection))
LoginFailureBadConnection = "Failed to connect to the Rally server or proxy.";
LoginFailureBadConnection = "Failed to connect to the CA Agile Central server or proxy.";

LoginFailureUnknown = loginFailureUnknown;
if (String.IsNullOrWhiteSpace(LoginFailureUnknown))
Expand Down Expand Up @@ -566,13 +566,13 @@ private RallyRestApi.AuthenticationResult PerformAuthenticationCheckAgainstRally
try
{
if (String.IsNullOrWhiteSpace(LoginDetails.RallyServer))
errorMessage = "Bad URI format for Rally Server";
errorMessage = "Bad URI format for CA Agile Central Server";
else
serverUri = new Uri(LoginDetails.RallyServer);
}
catch
{
errorMessage = "Bad URI format for Rally Server";
errorMessage = "Bad URI format for CA Agile Central Server";
}

try
Expand All @@ -585,7 +585,7 @@ private RallyRestApi.AuthenticationResult PerformAuthenticationCheckAgainstRally
}
catch (RallyUnavailableException)
{
errorMessage = "Rally is currently unavailable.";
errorMessage = "CA Agile Central is currently unavailable.";
}
catch (WebException e)
{
Expand Down Expand Up @@ -645,7 +645,7 @@ private RallyRestApi.AuthenticationResult PerformAuthenticationCheckAgainstIdp(o
}
catch
{
errorMessage = "Bad URI format for Rally Server";
errorMessage = "Bad URI format for CA Agile Central Server";
}

try
Expand All @@ -658,7 +658,7 @@ private RallyRestApi.AuthenticationResult PerformAuthenticationCheckAgainstIdp(o
}
catch (RallyUnavailableException)
{
errorMessage = "Rally is currently unavailable.";
errorMessage = "CA Agile Central is currently unavailable.";
}
catch (WebException e)
{
Expand Down
23 changes: 22 additions & 1 deletion Rally.RestApi/Auth/LoginDetails.cs
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ private string GetChildNodeValue(XmlNodeList childNodes, string childNodeName)
foreach (XmlElement node in childNodes)
{
if (node.Name.Equals(childNodeName, StringComparison.InvariantCultureIgnoreCase))
return node.InnerXml;
return node.InnerText;
}

return null;
Expand Down Expand Up @@ -277,5 +277,26 @@ internal void MarkUserAsLoggedOut()
SaveToDisk();
}
#endregion

#region RedirectIfIDPPointsAtLoginSSO
/// <summary>
/// HACK: Redirect to custom SSO page if attempting to connect to /login/sso
/// This workaround is for internet explorer 7 compatability due to excel and VSP
/// relying on IE7 as its embedded browser
/// </summary>
/// <returns></returns>
internal string RedirectIfIdpPointsAtLoginSso(string idpServer)
{
String[] parseIdpServer = idpServer.Split('&');
for (int i = 0; i < parseIdpServer.Length; i++)
{
if (parseIdpServer[i].StartsWith("TargetResource") && parseIdpServer[i].Contains("/login/sso"))
{
parseIdpServer[i] = parseIdpServer[i].Replace("/login/sso", "/slm/empty.sp");
}
}
return String.Join("&", parseIdpServer);
}
#endregion
}
}
2 changes: 1 addition & 1 deletion Rally.RestApi/Exceptions/RallyUnavailableException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class RallyUnavailableException : Exception
/// <param name="innerException">The exception that is the cause of the current exception.</param>
/// <param name="errorMessage">The HTML error message that was returned from Rally.</param>
internal RallyUnavailableException(Exception innerException, string errorMessage)
: base("Rally Unavailable", innerException)
: base("CA Agile Central Unavailable", innerException)
{
ErrorMessage = errorMessage;
}
Expand Down
4 changes: 4 additions & 0 deletions Rally.RestApi/Json/DynamicJsonObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ private object FormatSetValue(object value)
{
return value;
}
if (value is DateTime)
{
return ((DateTime)value).ToUniversalTime().ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff'Z'");
}
throw new ArgumentException("Attempt to set property to an unsupported type.");
}

Expand Down
8 changes: 4 additions & 4 deletions Rally.RestApi/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Rally Rest API for .NET")]
[assembly: AssemblyTitle("CA Agile Central Rest API for .NET")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Rally Software, Inc.")]
[assembly: AssemblyProduct("Rally Rest API for .NET")]
[assembly: AssemblyCopyright("Copyright © Rally Software 2013")]
[assembly: AssemblyCompany("CA Technologies")]
[assembly: AssemblyProduct("CA Agile Central Rest API for .NET")]
[assembly: AssemblyCopyright("Copyright © CA Technologie 2013")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

Expand Down
Loading