Skip to content

Commit

Permalink
Add SSO application manangement
Browse files Browse the repository at this point in the history
  • Loading branch information
itaihanski committed Apr 18, 2024
1 parent 99e38f8 commit 0da2b35
Show file tree
Hide file tree
Showing 7 changed files with 578 additions and 1 deletion.
146 changes: 146 additions & 0 deletions Descope.Test/IntegrationTests/Management/SsoApplicationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
using Xunit;

namespace Descope.Test.Integration
{
public class SsoApplicationTests
{
private readonly DescopeClient _descopeClient = IntegrationTestSetup.InitDescopeClient();

[Fact]
public async Task SsoApplication_SamlCreateUpdateAndDelete()
{
string? id = null;
try
{
var name = "name";
var url = "https://sometestidp.com";
// Create
var options = new SamlApplicationOptions(name, url);
id = await _descopeClient.Management.SsoApplication.CreateSAMLApplication(options);

// Load
var loadedApp = await _descopeClient.Management.SsoApplication.Load(id);
Assert.Equal(name, loadedApp.Name);
Assert.Equal(url, loadedApp.SamlSettings!.LoginPageUrl);

// Update
options.Id = id;
name = "updated name";
url = "https://someothertestidp.com";
await _descopeClient.Management.SsoApplication.UpdateSAMLApplication(options);

// Load All
var apps = await _descopeClient.Management.SsoApplication.LoadAll();
loadedApp = apps.Find(a => a.Id == id);
Assert.Equal(name, loadedApp!.Name);

Check failure on line 35 in Descope.Test/IntegrationTests/Management/SsoApplicationTests.cs

View workflow job for this annotation

GitHub Actions / .NET Tests

Descope.Test.Integration.SsoApplicationTests ► SsoApplication_SamlCreateUpdateAndDelete

Failed test found in: TestResults/_fv-az1487-682_2024-04-18_10_38_54.trx TestResults/_fv-az1487-682_2024-04-18_10_39_23.trx Error: Assert.Equal() Failure: Strings differ ↓ (pos 0) Expected: "updated name" Actual: "name" ↑ (pos 0)
Raw output
Assert.Equal() Failure: Strings differ
           ↓ (pos 0)
Expected: "updated name"
Actual:   "name"
           ↑ (pos 0)
   at Descope.Test.Integration.SsoApplicationTests.SsoApplication_SamlCreateUpdateAndDelete() in /home/runner/work/descope-dotnet/descope-dotnet/Descope.Test/IntegrationTests/Management/SsoApplicationTests.cs:line 35
   at Descope.Test.Integration.SsoApplicationTests.SsoApplication_SamlCreateUpdateAndDelete() in /home/runner/work/descope-dotnet/descope-dotnet/Descope.Test/IntegrationTests/Management/SsoApplicationTests.cs:line 47
--- End of stack trace from previous location ---
Assert.Equal(url, loadedApp.SamlSettings!.LoginPageUrl);

// Delete
await _descopeClient.Management.SsoApplication.Delete(id);
id = null;
}
finally
{
if (!string.IsNullOrEmpty(id))
{
try { await _descopeClient.Management.SsoApplication.Delete(id); }
catch { }
}
}
}

[Fact]
public async Task Sso_SamlByMetadata()
{
string? tenantId = null;
string? roleName = null;
try
{
// Create a tenant
tenantId = await _descopeClient.Management.Tenant.Create(new TenantOptions(Guid.NewGuid().ToString()));
roleName = Guid.NewGuid().ToString()[..20];
await _descopeClient.Management.Role.Create(roleName, tenantId: tenantId);

// update sso settings
var settings = new SsoSamlSettingsByMetadata("https://sometestidpmd.com")
{
RoleMappings = new List<RoleMapping> { new RoleMapping(new List<string> { "group1", "group2" }, roleName) }
};
await _descopeClient.Management.Sso.ConfigureSamlSettingsByMetadata(tenantId, settings, "https://myredirect.com", new List<string> { "domain1.com" });

var loadedSetting = await _descopeClient.Management.Sso.LoadSettings(tenantId);

// Make sure the settings match
Assert.Equal(settings.IdpMetadataUrl, loadedSetting.Saml.IdpMetadataUrl);
Assert.NotEmpty(loadedSetting.Saml.GroupsMapping?.First()?.Role?.Id ?? "");
Assert.Equal("group1", loadedSetting.Saml.GroupsMapping?.First()?.Groups?[0]);
Assert.Equal("group2", loadedSetting.Saml.GroupsMapping?.First()?.Groups?[1]);
Assert.Equal("https://myredirect.com", loadedSetting.Saml?.RedirectUrl);
Assert.Equal("domain1.com", loadedSetting.Tenant.Domains.First());
}
finally
{
if (!string.IsNullOrEmpty(tenantId))
{
try { await _descopeClient.Management.Tenant.Delete(tenantId); }
catch { }
}
if (!string.IsNullOrEmpty(roleName))
{
try { await _descopeClient.Management.Role.Delete(roleName); }
catch { }
}
}
}

[Fact]
public async Task Sso_Oidc()
{
string? tenantId = null;
string? roleName = null;
try
{
// Create a tenant
tenantId = await _descopeClient.Management.Tenant.Create(new TenantOptions(Guid.NewGuid().ToString()));
roleName = Guid.NewGuid().ToString()[..20];
await _descopeClient.Management.Role.Create(roleName, tenantId: tenantId);

// Update sso settings
var settings = new SsoOidcSettings
{
Name = "Name",
ClientId = "ClientId",
ClientSecret = "ClientSecret",
AuthUrl = "https://mytestauth.com",
TokenUrl = "https://mytestauth.com",
JwksUrl = "https://mytestauth.com",
AttributeMapping = new OidcAttributeMapping { }
};
await _descopeClient.Management.Sso.ConfigureOidcSettings(tenantId, settings, new List<string> { "domain1.com" });

var loadedSetting = await _descopeClient.Management.Sso.LoadSettings(tenantId);

// Make sure the settings match
Assert.Equal(settings.Name, loadedSetting.Oidc.Name);
Assert.Equal(settings.ClientId, loadedSetting.Oidc.ClientId);
Assert.Equal(settings.AuthUrl, loadedSetting.Oidc.AuthUrl);
Assert.Equal(settings.TokenUrl, loadedSetting.Oidc.TokenUrl);
Assert.Equal(settings.JwksUrl, loadedSetting.Oidc.JwksUrl);
Assert.Equal("domain1.com", loadedSetting.Tenant.Domains.First());
}
finally
{
if (!string.IsNullOrEmpty(tenantId))
{
try { await _descopeClient.Management.Tenant.Delete(tenantId); }
catch { }
}
if (!string.IsNullOrEmpty(roleName))
{
try { await _descopeClient.Management.Role.Delete(roleName); }
catch { }
}
}
}
}
}
12 changes: 12 additions & 0 deletions Descope/Internal/Http/Routes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,18 @@ public static class Routes

#endregion SSO

#region SSO Application

public const string SsoApplicationOidcCreate = "/v1/mgmt/sso/idp/app/oidc/create";
public const string SsoApplicationSamlCreate = "/v1/mgmt/sso/idp/app/saml/create";
public const string SsoApplicationOidcUpdate = "/v1/mgmt/sso/idp/app/oidc/update";
public const string SsoApplicationSamlUpdate = "/v1/mgmt/sso/idp/app/saml/update";
public const string SsoApplicationDelete = "/v1/mgmt/sso/idp/app/delete";
public const string SsoApplicationLoad = "/v1/mgmt/sso/idp/app/load";
public const string SsoApplicationLoadAll = "/v1/mgmt/sso/idp/apps/load";

#endregion SSO Application

#region Permission

public const string PermissionCreate = "/v1/mgmt/permission/create";
Expand Down
3 changes: 3 additions & 0 deletions Descope/Internal/Management/Managment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ internal class Management : IManagement
public IUser User => _user;
public IAccessKey AccessKey => _accessKey;
public ISso Sso => _sso;
public ISsoApplication SsoApplication => _ssoApplication;
public IPasswordSettings Password => _password;
public IJwt Jwt => _jwt;
public IPermission Permission => _permission;
Expand All @@ -16,6 +17,7 @@ internal class Management : IManagement
private readonly User _user;
private readonly AccessKey _accessKey;
private readonly Sso _sso;
private readonly SsoApplication _ssoApplication;
private readonly Password _password;
private readonly Jwt _jwt;
private readonly Permission _permission;
Expand All @@ -28,6 +30,7 @@ public Management(IHttpClient client, string managementKey)
_user = new User(client, managementKey);
_accessKey = new AccessKey(client, managementKey);
_sso = new Sso(client, managementKey);
_ssoApplication = new SsoApplication(client, managementKey);
_password = new Password(client, managementKey);
_jwt = new Jwt(client, managementKey);
_permission = new Permission(client, managementKey);
Expand Down
80 changes: 80 additions & 0 deletions Descope/Internal/Management/SsoApplication.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@

using System.Text.Json.Serialization;

namespace Descope.Internal.Management
{
internal class SsoApplication : ISsoApplication
{
private readonly IHttpClient _httpClient;
private readonly string _managementKey;

internal SsoApplication(IHttpClient httpClient, string managementKey)
{
_httpClient = httpClient;
_managementKey = managementKey;
}

public async Task<string> CreateOidcApplication(OidcApplicationOptions options)
{
var resp = await _httpClient.Post<SsoApplicationCreateResponse>(Routes.SsoApplicationOidcCreate, _managementKey, body: options);
return resp.Id;
}

public async Task<string> CreateSAMLApplication(SamlApplicationOptions options)
{
var resp = await _httpClient.Post<SsoApplicationCreateResponse>(Routes.SsoApplicationSamlCreate, _managementKey, body: options);
return resp.Id;
}

public async Task UpdateOIDCApplication(OidcApplicationOptions options)
{
await _httpClient.Post<object>(Routes.SsoApplicationOidcUpdate, _managementKey, body: options);
}

public async Task UpdateSAMLApplication(SamlApplicationOptions options)
{
await _httpClient.Post<object>(Routes.SsoApplicationSamlUpdate, _managementKey, body: options);
}

public async Task Delete(string id)
{
var body = new { id };
await _httpClient.Post<object>(Routes.SsoApplicationDelete, _managementKey, body: body);
}

public async Task<SsoApplicationResponse> Load(string id)
{
return await _httpClient.Get<SsoApplicationResponse>(Routes.SsoApplicationLoad, _managementKey, queryParams: new Dictionary<string, string?> { { "id", id } });
}

public async Task<List<SsoApplicationResponse>> LoadAll()
{
var resp = await _httpClient.Get<SsoLoadAllResponse>(Routes.SsoApplicationLoadAll, _managementKey);
return resp.Apps;
}

}

internal class SsoApplicationCreateResponse
{
[JsonPropertyName("id")]
public string Id { get; set; }

public SsoApplicationCreateResponse(string id)
{
Id = id;
}
}

internal class SsoLoadAllResponse
{
[JsonPropertyName("apps")]
public List<SsoApplicationResponse> Apps { get; set; }

public SsoLoadAllResponse(List<SsoApplicationResponse> apps)
{
Apps = apps;
}
}

}
65 changes: 65 additions & 0 deletions Descope/Sdk/Managment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,66 @@ public interface ISso
Task DeleteSettings(string tenantId);
}

/// <summary>
/// Provides functions for managing SSO applications in a project.
/// </summary>
public interface ISsoApplication
{
/// <summary>
/// Create a new OIDC SSO application according to the given options.
/// </summary>
/// <param name="options">Options to define an OIDC application</param>
/// <returns>The created application ID</returns>
public Task<string> CreateOidcApplication(OidcApplicationOptions options);

/// <summary>
/// Create a new SAML SSO application according to the given options.
/// </summary>
/// <param name="options"></param>
/// <returns>The created application ID</returns>
public Task<string> CreateSAMLApplication(SamlApplicationOptions options);

/// <summary>
/// Update an existing OIDC SSO application.
/// <para>
/// <b>IMPORTANT:</b> All options are taken as-is and will override whatever value is currently set. Use carefully.
/// </para>
/// </summary>
/// <param name="options">The options to set for an existing SSO application</param>
public Task UpdateOIDCApplication(OidcApplicationOptions options);

/// <summary>
/// Update an existing SAML SSO application.
/// <para>
/// <b>IMPORTANT:</b> All options are taken as-is and will override whatever value is currently set. Use carefully.
/// </para>
/// </summary>
/// <param name="options">The options to set for an existing SSO application</param>
public Task UpdateSAMLApplication(SamlApplicationOptions options);

/// <summary>
/// Delete an existing SSO application.
/// <para>
/// <b>IMPORTANT:</b> This action is irreversible. Use carefully.
/// </para>
/// </summary>
/// <param name="id">The ID of application to delete</param>
public Task Delete(string id);

/// <summary>
/// Load an SSO application
/// </summary>
/// <param name="id">The ID of the application to load</param>
/// <returns>The loaded SSO application</returns>
public Task<SsoApplicationResponse> Load(string id);

/// <summary>
/// Load all project SSO applications.
/// </summary>
/// <returns>A list of all SSO applications.</returns>
public Task<List<SsoApplicationResponse>> LoadAll();
}

/// <summary>
/// Provides functions for managing permissions in a project.
/// </summary>
Expand Down Expand Up @@ -788,6 +848,11 @@ public interface IManagement
/// </summary>
public ISso Sso { get; }

/// <summary>
/// Provides functions for configuring SSO Applications in a project.
/// </summary>
public ISsoApplication SsoApplication { get; }

/// <summary>
/// Provides functions for managing password policy for a project or a tenant.
/// </summary>
Expand Down
Loading

0 comments on commit 0da2b35

Please sign in to comment.