Skip to content

Commit

Permalink
Add password settings management (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
itaihanski authored Mar 26, 2024
1 parent 9d81a36 commit f77ec86
Show file tree
Hide file tree
Showing 9 changed files with 216 additions and 10 deletions.
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

0 comments on commit f77ec86

Please sign in to comment.