Skip to content
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

Add password settings management #15

Merged
merged 1 commit into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
43 changes: 43 additions & 0 deletions Descope.Test/IntegrationTests/Management/PasswordSettingsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using Xunit;

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

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

// update project level
var settings = await _descopeClient.Management.Password.GetSettings();
settings.MinLength = 6;
await _descopeClient.Management.Password.ConfigureSettings(settings);

// update tenant level
settings.MinLength = 7;
await _descopeClient.Management.Password.ConfigureSettings(settings, tenantId);

// make sure changes don't clash
var projectSettings = await _descopeClient.Management.Password.GetSettings();
Assert.Equal(6, projectSettings.MinLength);
var tenantSettings = await _descopeClient.Management.Password.GetSettings(tenantId);
Assert.Equal(7, tenantSettings.MinLength);
}
finally
{
if (!string.IsNullOrEmpty(tenantId))
{
try { await _descopeClient.Management.Tenant.Delete(tenantId); }
catch { }
}
}
}
}
}
4 changes: 2 additions & 2 deletions Descope.Test/IntegrationTests/Management/UserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -540,11 +540,11 @@ public async Task User_Password()
Assert.False(createResult.Password);

// Set a temporary password
await _descopeClient.Management.User.SetActivePassword(loginId, "abCD123#$");
await _descopeClient.Management.User.SetActivePassword(loginId, "abCD123#$abCD123#$");
var loadResult = await _descopeClient.Management.User.Load(loginId);
Assert.True(loadResult.Password);
await _descopeClient.Management.User.ExpirePassword(loginId);
await _descopeClient.Management.User.SetTemporaryPassword(loginId, "abCD123#$");
await _descopeClient.Management.User.SetTemporaryPassword(loginId, "abCD123#$abCD123#$");
}
finally
{
Expand Down
24 changes: 17 additions & 7 deletions Descope/Internal/Http/HttpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ public interface IHttpClient
{
DescopeConfig DescopeConfig { get; set; }

Task<TResponse> Get<TResponse>(string resource, string? pswd = null);
Task<TResponse> Get<TResponse>(string resource, string? pswd = null, Dictionary<string, string?>? queryParams = null);

Task<TResponse> Post<TResponse>(string resource, string? pswd = null, object? body = null);
Task<TResponse> Post<TResponse>(string resource, string? pswd = null, object? body = null, Dictionary<string, string?>? queryParams = null);

Task<TResponse> Delete<TResponse>(string resource, string pswd);
}
Expand Down Expand Up @@ -37,22 +37,22 @@ public HttpClient(DescopeConfig descopeConfig)
_client.AddDefaultHeader("x-descope-sdk-dotnet-version", Environment.Version.ToString());
}

public async Task<TResponse> Get<TResponse>(string resource, string? pswd = null)
public async Task<TResponse> Get<TResponse>(string resource, string? pswd = null, Dictionary<string, string?>? queryParams = null)
{
return await Call<TResponse>(resource, Method.Get, pswd);
return await Call<TResponse>(resource, Method.Get, pswd, queryParams: queryParams);
}

public async Task<TResponse> Post<TResponse>(string resource, string? pswd = null, object? body = null)
public async Task<TResponse> Post<TResponse>(string resource, string? pswd = null, object? body = null, Dictionary<string, string?>? queryParams = null)
{
return await Call<TResponse>(resource, Method.Post, pswd, body);
return await Call<TResponse>(resource, Method.Post, pswd, body: body, queryParams: queryParams);
}

public async Task<TResponse> Delete<TResponse>(string resource, string? pswd = null)
{
return await Call<TResponse>(resource, Method.Delete, pswd);
}

private async Task<TResponse> Call<TResponse>(string resource, Method method, string? pswd, object? body = null)
private async Task<TResponse> Call<TResponse>(string resource, Method method, string? pswd, object? body = null, Dictionary<string, string?>? queryParams = null)
{
var request = new RestRequest(resource, method);

Expand All @@ -61,12 +61,22 @@ private async Task<TResponse> Call<TResponse>(string resource, Method method, st
if (!string.IsNullOrEmpty(pswd)) bearer = $"{bearer}:{pswd}";
request.AddHeader("Authorization", "Bearer " + bearer);

// Add body if available
if (body != null)
{
var jsonBody = JsonSerializer.Serialize(body);
request.AddJsonBody(jsonBody);
}

// Add query params if available
if (queryParams != null)
{
foreach (var param in queryParams)
{
request.AddQueryParameter(param.Key, param.Value);
}
}

var response = await _client.ExecuteAsync<TResponse>(request);

if (response.StatusCode == System.Net.HttpStatusCode.OK)
Expand Down
6 changes: 6 additions & 0 deletions Descope/Internal/Http/Routes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ public static class Routes

#endregion User

#region Password

public const string PasswordSettings = "/v1/mgmt/password/settings";

#endregion Password

#region JWT

public const string JwtUpdate = "/v1/mgmt/jwt/update";
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 @@ -5,6 +5,7 @@ internal class Management : IManagement
public ITenant Tenant => _tenant;
public IUser User => _user;
public IAccessKey AccessKey => _accessKey;
public IPasswordSettings Password => _password;
public IJwt Jwt => _jwt;
public IPermission Permission => _permission;
public IRole Role => _role;
Expand All @@ -13,6 +14,7 @@ internal class Management : IManagement
private readonly Tenant _tenant;
private readonly User _user;
private readonly AccessKey _accessKey;
private readonly Password _password;
private readonly Jwt _jwt;
private readonly Permission _permission;
private readonly Role _role;
Expand All @@ -23,6 +25,7 @@ public Management(IHttpClient client, string managementKey)
_tenant = new Tenant(client, managementKey);
_user = new User(client, managementKey);
_accessKey = new AccessKey(client, managementKey);
_password = new Password(client, managementKey);
_jwt = new Jwt(client, managementKey);
_permission = new Permission(client, managementKey);
_role = new Role(client, managementKey);
Expand Down
50 changes: 50 additions & 0 deletions Descope/Internal/Management/Password.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System.Text.Json.Serialization;

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

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

public async Task<PasswordSettings> GetSettings(string? tenantId = null)
{
return await _httpClient.Get<PasswordSettings>(Routes.PasswordSettings, _managementKey, queryParams: new Dictionary<string, string?> { { "tenantId", tenantId } });
}

public async Task ConfigureSettings(PasswordSettings settings, string? tenantId = null)
{
var body = new WrappedSettings
{
TenantId = tenantId,
Enabled = settings.Enabled,
MinLength = settings.MinLength,
Lowercase = settings.Lowercase,
Uppercase = settings.Uppercase,
Number = settings.Number,
NonAlphanumeric = settings.NonAlphanumeric,
Expiration = settings.Expiration,
ExpirationWeeks = settings.ExpirationWeeks,
Reuse = settings.Reuse,
ReuseAmount = settings.ReuseAmount,
Lock = settings.Lock,
LockAttempts = settings.LockAttempts,
};
await _httpClient.Post<object>(Routes.PasswordSettings, _managementKey, body);
}

}

internal class WrappedSettings : PasswordSettings
{
[JsonPropertyName("tenantId")]
public string? TenantId { get; set; }
}

}
31 changes: 30 additions & 1 deletion Descope/Sdk/Managment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,30 @@ public interface IRole
Task<List<RoleResponse>> SearchAll(RoleSearchOptions? options);
}

/// <summary>
/// Provides functions for managing password policy for a project or a tenant.
/// </summary>
public interface IPasswordSettings
{
/// <summary>
/// Get password settings for a project or tenant.
/// </summary>
/// <param name="tenantId">Optionally scope the settings to a tenant</param>
/// <returns>The current password settings</returns>
Task<PasswordSettings> GetSettings(string? tenantId = null);

/// <summary>
/// Configure Password settings for a project or a tenant manually.
/// <para>
/// <b>NOTE:</b> The settings parameter is taken as is and overrides any current settings.
/// Use carefully.
/// </para>
/// </summary>
/// <param name="settings">The settings to set - taken as is</param>
/// <param name="tenantId">Optionally scope the settings to a tenant</param>
Task ConfigureSettings(PasswordSettings settings, string? tenantId = null);
}

/// <summary>
/// Provide functions for manipulating valid JWT
/// </summary>
Expand All @@ -624,7 +648,7 @@ public interface IJwt
/// <summary>
/// Impersonate another user
/// <para>
/// The impersonator user must have the <c>Impersonation</c> permission in order for this request to work
/// The impersonator user must have the <c>Impersonation</c> permission in order for this request to work
/// </para>
/// </summary>
/// <param name="impersonatorId">The user ID performing the impersonation</param>
Expand Down Expand Up @@ -703,6 +727,11 @@ public interface IManagement
/// </summary>
public IAccessKey AccessKey { get; }

/// <summary>
/// Provides functions for managing password policy for a project or a tenant.
/// </summary>
public IPasswordSettings Password { get; }

/// <summary>
/// Provides functions for manipulating valid JWTs
/// </summary>
Expand Down
28 changes: 28 additions & 0 deletions Descope/Types/Types.cs
Original file line number Diff line number Diff line change
Expand Up @@ -646,4 +646,32 @@ public class RoleSearchOptions
[JsonPropertyName("permissionNames")]
public List<string>? PermissionNames { get; set; }
}

public class PasswordSettings
{
[JsonPropertyName("enabled")]
public bool Enabled { get; set; }
[JsonPropertyName("minLength")]
public int MinLength { get; set; }
[JsonPropertyName("lowercase")]
public bool Lowercase { get; set; }
[JsonPropertyName("uppercase")]
public bool Uppercase { get; set; }
[JsonPropertyName("number")]
public bool Number { get; set; }
[JsonPropertyName("nonAlphanumeric")]
public bool NonAlphanumeric { get; set; }
[JsonPropertyName("expiration")]
public bool Expiration { get; set; }
[JsonPropertyName("expirationWeeks")]
public int ExpirationWeeks { get; set; }
[JsonPropertyName("reuse")]
public bool Reuse { get; set; }
[JsonPropertyName("reuseAmount")]
public int ReuseAmount { get; set; }
[JsonPropertyName("lock")]
public bool Lock { get; set; }
[JsonPropertyName("lockAttempts")]
public int LockAttempts { get; set; }
}
}
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,43 @@ var loginOptions = new AccessKeyLoginOptions
var token = await descopeClient.Auth.ExchangeAccessKey("accessKey", loginOptions);
```

### Manage Password Settings

You can manage password settings for your project or tenants.

```cs
try
{
// You can get password settings for the project or for a specific tenant ID.
var settings = await _descopeClient.Management.Password.GetSettings("optional-tenant-id");

// You can configure the project level settings, by leaving the optional tenant ID empty,
// or tenant level password settings by providing a tenant ID.
// The update is performed as-is in an overriding manner - use carefully.
var updatedSettings = new PasswordSettings
{
Enabled = true,
MinLength = 8,
Lowercase = true,
Uppercase = true,
Number = true,
NonAlphanumeric = true,
Expiration = true,
ExpirationWeeks = 3,
Reuse = true,
ReuseAmount = 3,
Lock = true,
LockAttempts = 5,
};
await _descopeClient.Management.Password.ConfigureSettings(updatedSettings, "optional-tenant-id");

}
catch (DescopeException e)
{
// handle errors
}
```

### Manage and Manipulate JWTs

You can update custom claims on a valid JWT or even impersonate a different user - as long as the
Expand Down