diff --git a/Descope.Test/IntegrationTests/Management/SsoApplicationTests.cs b/Descope.Test/IntegrationTests/Management/SsoApplicationTests.cs new file mode 100644 index 0000000..78145f4 --- /dev/null +++ b/Descope.Test/IntegrationTests/Management/SsoApplicationTests.cs @@ -0,0 +1,97 @@ +using Xunit; + +namespace Descope.Test.Integration +{ + public class SsoApplicationTests + { + private readonly DescopeClient _descopeClient = IntegrationTestSetup.InitDescopeClient(); + + [Fact] + public async Task SsoApplication_Saml() + { + string? id = null; + try + { + var name = "saml_app"; + 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 + name = "saml_app_updated"; + url = "https://sometestidp.com/updated"; + options = new SamlApplicationOptions(name, url) { Id = id }; + 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); + 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 SsoApplication_Oidc() + { + string? id = null; + try + { + var name = "oidc_app"; + var url = "https://sometestidp.com"; + // Create + var options = new OidcApplicationOptions(name, url); + id = await _descopeClient.Management.SsoApplication.CreateOidcApplication(options); + + // Load + var loadedApp = await _descopeClient.Management.SsoApplication.Load(id); + Assert.Equal(name, loadedApp.Name); + Assert.Equal(url, loadedApp.OidcSettings!.LoginPageUrl); + + // Update + name = "oidc_app_updated"; + url = "https://sometestidp.com/updated"; + options = new OidcApplicationOptions(name, url) { Id = id }; + await _descopeClient.Management.SsoApplication.UpdateOidcApplication(options); + + // Load All + var apps = await _descopeClient.Management.SsoApplication.LoadAll(); + loadedApp = apps.Find(a => a.Id == id); + Assert.Equal(name, loadedApp!.Name); + Assert.Equal(url, loadedApp.OidcSettings!.LoginPageUrl); + + // Delete + await _descopeClient.Management.SsoApplication.Delete(id); + id = null; + } + finally + { + if (!string.IsNullOrEmpty(id)) + { + try { await _descopeClient.Management.SsoApplication.Delete(id); } + catch { } + } + } + } + + } + +} diff --git a/Descope/Internal/Http/Routes.cs b/Descope/Internal/Http/Routes.cs index cfdcc14..1ec546c 100644 --- a/Descope/Internal/Http/Routes.cs +++ b/Descope/Internal/Http/Routes.cs @@ -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"; diff --git a/Descope/Internal/Management/Managment.cs b/Descope/Internal/Management/Managment.cs index 86e530d..89df6ed 100644 --- a/Descope/Internal/Management/Managment.cs +++ b/Descope/Internal/Management/Managment.cs @@ -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; @@ -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; @@ -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); diff --git a/Descope/Internal/Management/SsoApplication.cs b/Descope/Internal/Management/SsoApplication.cs new file mode 100644 index 0000000..6e7a49d --- /dev/null +++ b/Descope/Internal/Management/SsoApplication.cs @@ -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 CreateOidcApplication(OidcApplicationOptions options) + { + var resp = await _httpClient.Post(Routes.SsoApplicationOidcCreate, _managementKey, body: options); + return resp.Id; + } + + public async Task CreateSAMLApplication(SamlApplicationOptions options) + { + var resp = await _httpClient.Post(Routes.SsoApplicationSamlCreate, _managementKey, body: options); + return resp.Id; + } + + public async Task UpdateOidcApplication(OidcApplicationOptions options) + { + await _httpClient.Post(Routes.SsoApplicationOidcUpdate, _managementKey, body: options); + } + + public async Task UpdateSamlApplication(SamlApplicationOptions options) + { + await _httpClient.Post(Routes.SsoApplicationSamlUpdate, _managementKey, body: options); + } + + public async Task Delete(string id) + { + var body = new { id }; + await _httpClient.Post(Routes.SsoApplicationDelete, _managementKey, body: body); + } + + public async Task Load(string id) + { + return await _httpClient.Get(Routes.SsoApplicationLoad, _managementKey, queryParams: new Dictionary { { "id", id } }); + } + + public async Task> LoadAll() + { + var resp = await _httpClient.Get(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 Apps { get; set; } + + public SsoLoadAllResponse(List apps) + { + Apps = apps; + } + } + +} diff --git a/Descope/Sdk/Managment.cs b/Descope/Sdk/Managment.cs index 29d243f..6e17c47 100644 --- a/Descope/Sdk/Managment.cs +++ b/Descope/Sdk/Managment.cs @@ -572,6 +572,66 @@ public interface ISso Task DeleteSettings(string tenantId); } + /// + /// Provides functions for managing SSO applications in a project. + /// + public interface ISsoApplication + { + /// + /// Create a new OIDC SSO application according to the given options. + /// + /// Options to define an OIDC application + /// The created application ID + public Task CreateOidcApplication(OidcApplicationOptions options); + + /// + /// Create a new SAML SSO application according to the given options. + /// + /// + /// The created application ID + public Task CreateSAMLApplication(SamlApplicationOptions options); + + /// + /// Update an existing OIDC SSO application. + /// + /// IMPORTANT: All options are taken as-is and will override whatever value is currently set. Use carefully. + /// + /// + /// The options to set for an existing SSO application + public Task UpdateOidcApplication(OidcApplicationOptions options); + + /// + /// Update an existing SAML SSO application. + /// + /// IMPORTANT: All options are taken as-is and will override whatever value is currently set. Use carefully. + /// + /// + /// The options to set for an existing SSO application + public Task UpdateSamlApplication(SamlApplicationOptions options); + + /// + /// Delete an existing SSO application. + /// + /// IMPORTANT: This action is irreversible. Use carefully. + /// + /// + /// The ID of application to delete + public Task Delete(string id); + + /// + /// Load an SSO application + /// + /// The ID of the application to load + /// The loaded SSO application + public Task Load(string id); + + /// + /// Load all project SSO applications. + /// + /// A list of all SSO applications. + public Task> LoadAll(); + } + /// /// Provides functions for managing permissions in a project. /// @@ -788,6 +848,11 @@ public interface IManagement /// public ISso Sso { get; } + /// + /// Provides functions for configuring SSO Applications in a project. + /// + public ISsoApplication SsoApplication { get; } + /// /// Provides functions for managing password policy for a project or a tenant. /// diff --git a/Descope/Types/Sso.cs b/Descope/Types/Sso.cs new file mode 100644 index 0000000..24441bc --- /dev/null +++ b/Descope/Types/Sso.cs @@ -0,0 +1,272 @@ +using System.Text.Json.Serialization; + +namespace Descope +{ + + /// + /// Options to create or update an OIDC application. + /// + public class OidcApplicationOptions + { + /// + /// Optional SSO application ID. If not provided, will be auto-generated. + /// + [JsonPropertyName("id")] + public string? Id { get; set; } + /// + /// The SSO application's name. Must be unique per project. + /// + [JsonPropertyName("name")] + public string Name { get; set; } + /// + /// Optional SSO application description. + /// + [JsonPropertyName("description")] + public string? Description { get; set; } + /// + /// Optionally set the SSO application as enabled or disabled. + /// + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } + /// + /// Optional SSO application logo. + /// + [JsonPropertyName("logo")] + public string? Logo { get; set; } + /// + /// The URL where login page is hosted. + /// + [JsonPropertyName("loginPageUrl")] + public string LoginPageUrl { get; set; } + public OidcApplicationOptions(string name, string loginPageUrl) + { + Name = name; + LoginPageUrl = loginPageUrl; + } + } + + /// + /// Options to create or update a SAML application. + /// + public class SamlApplicationOptions + { + /// + /// Optional SSO application ID. If not provided, will be auto-generated. + /// + [JsonPropertyName("id")] + public string? Id { get; set; } + /// + /// The SSO application's name. Must be unique per project. + /// + [JsonPropertyName("name")] + public string Name { get; set; } + /// + /// Optional SSO application description. + /// + [JsonPropertyName("description")] + public string? Description { get; set; } + /// + /// Optionally set the SSO application as enabled or disabled. + /// + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } + /// + /// Optional SSO application logo. + /// + [JsonPropertyName("logo")] + public string? Logo { get; set; } + /// + /// The URL where login page is hosted. + /// + [JsonPropertyName("loginPageUrl")] + public string LoginPageUrl { get; set; } + /// + /// Optionally determine whether SP info should be automatically fetched from MetadataURL + /// or by specifying it explicitly via the EntityId, AcsUrl and Certificate properties. + /// + [JsonPropertyName("useMetadataInfo")] + public bool UseMetadataInfo { get; set; } + /// + /// Optional SP metadata URL to fetch the SP SAML info from. Required if UseMetadataInfo is true. + /// + [JsonPropertyName("metadataUrl")] + public string? MetadataURL { get; set; } + /// + /// Optional SP entity ID. Required if UseMetadataInfo is false. + /// + [JsonPropertyName("entityId")] + public string? EntityId { get; set; } + /// + /// Optional SP ACS URL (SAML callback). Required if UseMetadataInfo is false. + /// + [JsonPropertyName("acsUrl")] + public string? AcsUrl { get; set; } + /// + /// Optional SP certificate. Required only when SAML request must be signed and UseMetadataInfo is false. + /// + [JsonPropertyName("certificate")] + public string? Certificate { get; set; } + /// + /// Optional mappings of Descope (IdP) attributes to SP attributes. + /// + [JsonPropertyName("attributeMapping")] + public List? AttributeMapping { get; set; } + /// + /// Optional mappings of Descope (IdP) roles to SP groups. + /// + [JsonPropertyName("groupsMapping")] + public List? GroupsMapping { get; set; } + /// + /// Optional list of URL wildcards. If provided, only URLs from this list will be accepted when receiving SAML callback requests. + /// + [JsonPropertyName("acsAllowedCallbacks")] + public List? AcsAllowedCallbacks { get; set; } + /// + /// Optionally define the SAML Assertion for the subject name type. Leave empty to use the Descope user ID or set to "email"/"phone". + /// + [JsonPropertyName("subjectNameIdType")] + public string? SubjectNameIDType { get; set; } + /// + /// Optionally define the SAML Assertion for subject name format. Defaults to "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified". + /// + [JsonPropertyName("subjectNameIdFormat")] + public string? SubjectNameIDFormat { get; set; } + public SamlApplicationOptions(string name, string loginPageURL) + { + Name = name; + LoginPageUrl = loginPageURL; + } + } + + + public class SamlIdpAttributeMappingInfo + { + [JsonPropertyName("name")] + public string Name { get; set; } + [JsonPropertyName("type")] + public string Type { get; set; } + [JsonPropertyName("value")] + public string Value { get; set; } + public SamlIdpAttributeMappingInfo(string name, string type, string value) + { + Name = name; + Type = type; + Value = value; + } + } + + public class SamlIdpGroupsMappingInfo + { + [JsonPropertyName("name")] + public string Name { get; set; } + [JsonPropertyName("type")] + public string Type { get; set; } + [JsonPropertyName("filterType")] + public string FilterType { get; set; } + [JsonPropertyName("value")] + public string Value { get; set; } + [JsonPropertyName("roles")] + public List Roles { get; set; } + public SamlIdpGroupsMappingInfo(string name, string type, string filterType, string value, List roles) + { + Name = name; + Type = type; + FilterType = filterType; + Value = value; + Roles = roles; + } + } + + public class SamlIdpRoleGroupMappingInfo + { + [JsonPropertyName("id")] + public string Id { get; set; } + [JsonPropertyName("name")] + public string Name { get; set; } + public SamlIdpRoleGroupMappingInfo(string id, string name) + { + Id = id; + Name = name; + } + } + + public class SsoApplicationResponse + { + [JsonPropertyName("id")] + public string Id { get; set; } + [JsonPropertyName("name")] + public string Name { get; set; } + [JsonPropertyName("description")] + public string? Description { get; set; } + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } + [JsonPropertyName("logo")] + public string? Logo { get; set; } + [JsonPropertyName("appType")] + public string AppType { get; set; } + [JsonPropertyName("samlSettings")] + public SsoApplicationSamlSettings? SamlSettings { get; set; } + [JsonPropertyName("oidcSettings")] + public SsoApplicationOidcSettings? OidcSettings { get; set; } + public SsoApplicationResponse(string id, string name, string appType) + { + Id = id; + Name = name; + AppType = appType; + } + } + + public class SsoApplicationSamlSettings + { + [JsonPropertyName("loginPageUrl")] + public string LoginPageUrl { get; set; } + [JsonPropertyName("idpCert")] + public string? IdpCert { get; set; } + [JsonPropertyName("useMetadataInfo")] + public bool UseMetadataInfo { get; set; } + [JsonPropertyName("metadataUrl")] + public string? MetadataUrl { get; set; } + [JsonPropertyName("entityId")] + public string? EntityId { get; set; } + [JsonPropertyName("acsUrl")] + public string? AcsUrl { get; set; } + [JsonPropertyName("certificate")] + public string? Certificate { get; set; } + [JsonPropertyName("attributeMapping")] + public List? AttributeMapping { get; set; } + [JsonPropertyName("groupsMapping")] + public List? GroupsMapping { get; set; } + [JsonPropertyName("idpMetadataUrl")] + public string? IdpMetadataUrl { get; set; } + [JsonPropertyName("idpEntityId")] + public string? IdpEntityId { get; set; } + [JsonPropertyName("idpSsoUrl")] + public string? IdpSsoUrl { get; set; } + [JsonPropertyName("acsAllowedCallbacks")] + public List? AcsAllowedCallbacks { get; set; } + [JsonPropertyName("subjectNameIdType")] + public string? SubjectNameIdType { get; set; } + [JsonPropertyName("subjectNameIdFormat")] + public string? SubjectNameIdFormat { get; set; } + public SsoApplicationSamlSettings(string loginPageUrl) + { + LoginPageUrl = loginPageUrl; + } + } + + public class SsoApplicationOidcSettings + { + [JsonPropertyName("loginPageUrl")] + public string LoginPageUrl { get; set; } + [JsonPropertyName("issuer")] + public string Issuer { get; set; } + [JsonPropertyName("discoveryUrl")] + public string DiscoveryUrl { get; set; } + public SsoApplicationOidcSettings(string loginPageUrl, string issuer, string discoveryUrl) + { + LoginPageUrl = loginPageUrl; + Issuer = issuer; + DiscoveryUrl = discoveryUrl; + } + } +} diff --git a/Descope/Types/Types.cs b/Descope/Types/Types.cs index 86c5e76..cbf195b 100644 --- a/Descope/Types/Types.cs +++ b/Descope/Types/Types.cs @@ -1,6 +1,5 @@ using System.Text.Json.Serialization; -using Descope.Internal.Management; using Microsoft.IdentityModel.JsonWebTokens; namespace Descope diff --git a/README.md b/README.md index 8a4b424..be3cdac 100644 --- a/README.md +++ b/README.md @@ -34,9 +34,13 @@ These sections show how to use the SDK to perform API management functions. Befo 1. [Manage Tenants](#manage-tenants) 2. [Manage Users](#manage-users) 3. [Manage Access Keys](#manage-access-keys) -4. [Manage Permissions](#manage-permissions) -5. [Manage Roles](#manage-roles) -6. [Manage Project](#manage-project) +4. [Manage SSO Settings](#manage-sso-setting) +5. [Manage SSO Applications](#manage-sso-applications) +6. [Manage Password Settings](#manage-password-settings) +7. [Manage and Manipulate JWTs](#manage-and-manipulate-jwts) +8. [Manage Permissions](#manage-permissions) +9. [Manage Roles](#manage-roles) +10. [Manage Project](#manage-project) --- @@ -549,6 +553,64 @@ Certifcate contents -----END CERTIFICATE----- ``` +### Manage SSO Applications + +You can create, update, delete or load sso applications: + +```cs +try +{ + // Create OIDC SSO application + var oidcOptions = new OidcApplicationOptions("My OIDC App", "http://loginurl.com") { Enabled = true }; + var appId = await descopeClient.Management.SsoApplication.CreateOidcApplication(oidcOptions); + + // Create SAML SSO application + var samlOptions = new SamlApplicationOptions("samlApp", "http://loginurl.com") + { + Id = samlAppID, + Enabled = true, + EntityID = "EntityID", + AcsURL = "http://dummy.com/acs", + Certificate: "cert", + AttributeMapping = new List { new ("attrName1", "attrType1", "attrValue1") }, + GroupsMapping = new List + { + new("grpName1", "grpType1", "grpFilterType1", "grpValue1", new List { new("rl1", "rlName1") }) + }, + }; + appId = await descopeClient.Management.SsoApplication.CreateSamlApplication(samlOptions); + + // Update OIDC SSO application + // Update will override all fields as is. Use carefully. + var oidcOptions = new OidcApplicationOptions("My OIDC App", "http://updated-loginurl.com") { Id = oidcAppId, Enabled = true }; + await descopeClient.Management.SsoApplication.UpdateOidcApplication(options); + + // Update SAML SSO application + // Update will override all fields as is. Use carefully. + var samlOptions = new SamlApplicationOptions("samlApp", "http://loginurl.com") + { + Id = samlAppID, + Enabled = true, + UseMetadataInfo = true, + MetadataURL = "https://metadata.com", + }; + await descopeClient.Management.SsoApplication.UpdateSamlApplication(samlOptions); + + // Load SSO application by id + var app = await descopeClient.Management.SsoApplication.Load( "appId"); + + // Load all SSO applications + var apps = await descopeClient.Management.SsoApplication.LoadAll(); + + // SSO application deletion cannot be undone. Use carefully. + await descopeClient.Management.SsoApplication.Delete("appId"); +} +catch +{ + // handle errors +} +``` + ### Manage Password Settings You can manage password settings for your project or tenants.