Skip to content

Password & tenant settings api #361

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

Merged
merged 13 commits into from
Jan 16, 2024
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,23 @@ if err == nil {
// Do something
}
}

// Load tenant settings by a tenant id
settings, err := descopeClient.Management.Tenant().GetSettings(context.Background())

settingsRequest := &descope.TenantSettings{}
settingsRequest.SelfProvisioningDomains = []string{"domain.com", "company.com"}
settingsRequest.RefreshTokenExpiration = 30
settingsRequest.RefreshTokenExpirationUnit = "days"
settingsRequest.SessionTokenExpiration = 30
settingsRequest.SessionTokenExpirationUnit = "minutes"
settingsRequest.EnableInactivity = true
settingsRequest.InactivityTime = 2
settingsRequest.InactivityTimeUnit = "days"

// update the tenant settings
err := descopeClient.Management.Tenant().ConfigureSettings(context.Background(), "My Tenant", settingsRequest)

```

### Manage Users
Expand Down Expand Up @@ -875,6 +892,38 @@ Certifcate contents
// To delete SSO settings, call the following method
err := descopeClient.Management.SSO().DeleteSettings(context.Background(), "tenant-id")

### Manage Password Setting

You can manage password settings for tenants and projects.

```go
// You can get password settings for a specific tenant ID. Empty tenant ID will return the project password settings.
settings, err := descopeClient.Management.Password().GetSettings(context.Background(), "tenant-id")

// You can configure password settings by setting the required fields directly and provide the tenant ID to update.
tenantID := "tenant-id" // Which tenant this configuration is for
settingsToUpdate := &descope.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,
EmailServiceProvider: "Descope",
EmailSubject: "<your-reset-email-subject>",
EmailBody: "<your-reset-email-html>",
EmailBodyPlainText: "<your-reset-email-text>",
UseEmailBodyPlainText: true,
}
err := descopeClient.Management.Password().ConfigureSettings(context.Background(), tenantID, settingsToUpdate)
```

### Manage Permissions

You can create, update, delete or load permissions:
Expand Down
13 changes: 13 additions & 0 deletions descope/api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ var (
tenantLoad: "mgmt/tenant",
tenantLoadAll: "mgmt/tenant/all",
tenantSearchAll: "mgmt/tenant/search",
tenantSettings: "mgmt/tenant/settings",
userCreate: "mgmt/user/create",
userCreateBatch: "mgmt/user/create/batch",
userUpdate: "mgmt/user/update",
Expand Down Expand Up @@ -125,6 +126,7 @@ var (
ssoOIDCSettings: "mgmt/sso/oidc",
ssoMetadata: "mgmt/sso/metadata",
ssoMapping: "mgmt/sso/mapping",
passwordSettings: "mgmt/password/settings",
updateJWT: "mgmt/jwt/update",
permissionCreate: "mgmt/permission/create",
permissionUpdate: "mgmt/permission/update",
Expand Down Expand Up @@ -237,6 +239,7 @@ type mgmtEndpoints struct {
tenantLoad string
tenantLoadAll string
tenantSearchAll string
tenantSettings string

userCreate string
userCreateBatch string
Expand Down Expand Up @@ -289,6 +292,8 @@ type mgmtEndpoints struct {
ssoOIDCSettings string
updateJWT string

passwordSettings string

permissionCreate string
permissionUpdate string
permissionDelete string
Expand Down Expand Up @@ -511,6 +516,10 @@ func (e *endpoints) ManagementTenantSearchAll() string {
return path.Join(e.version, e.mgmt.tenantSearchAll)
}

func (e *endpoints) ManagementTenantSettings() string {
return path.Join(e.version, e.mgmt.tenantSettings)
}

func (e *endpoints) ManagementUserCreate() string {
return path.Join(e.version, e.mgmt.userCreate)
}
Expand Down Expand Up @@ -678,6 +687,10 @@ func (e *endpoints) ManagementSSOMapping() string {
return path.Join(e.version, e.mgmt.ssoMapping)
}

func (e *endpoints) ManagementPasswordSettings() string {
return path.Join(e.version, e.mgmt.passwordSettings)
}

func (e *endpoints) ManagementUpdateJWT() string {
return path.Join(e.version, e.mgmt.updateJWT)
}
Expand Down
7 changes: 7 additions & 0 deletions descope/internal/mgmt/mgmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type managementService struct {
user sdk.User
accessKey sdk.AccessKey
sso sdk.SSO
password sdk.PasswordManagement
jwt sdk.JWT
permission sdk.Permission
role sdk.Role
Expand All @@ -49,6 +50,7 @@ func NewManagement(conf ManagementParams, c *api.Client) *managementService {
service.project = &project{managementBase: base}
service.audit = &audit{managementBase: base}
service.authz = &authz{managementBase: base}
service.password = &password{managementBase: base}
return service
}

Expand All @@ -72,6 +74,11 @@ func (mgmt *managementService) SSO() sdk.SSO {
return mgmt.sso
}

func (mgmt *managementService) Password() sdk.PasswordManagement {
mgmt.ensureManagementKey()
return mgmt.password
}

func (mgmt *managementService) JWT() sdk.JWT {
mgmt.ensureManagementKey()
return mgmt.jwt
Expand Down
58 changes: 58 additions & 0 deletions descope/internal/mgmt/password.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package mgmt

import (
"context"

"github.com/descope/go-sdk/descope"
"github.com/descope/go-sdk/descope/api"
"github.com/descope/go-sdk/descope/internal/utils"
)

type password struct {
managementBase
}

func (s *password) GetSettings(ctx context.Context, tenantID string) (*descope.PasswordSettings, error) {
req := &api.HTTPRequest{
QueryParams: map[string]string{"tenantId": tenantID},
}
res, err := s.client.DoGetRequest(ctx, api.Routes.ManagementPasswordSettings(), req, s.conf.ManagementKey)
if err != nil {
return nil, err
}
return unmarshalPasswordSettingsResponse(res)
}

func (s *password) ConfigureSettings(ctx context.Context, tenantID string, passwordSettings *descope.PasswordSettings) error {
req := map[string]any{
"tenantId": tenantID,
"enabled": passwordSettings.Enabled,
"minLength": passwordSettings.MinLength,
"lowercase": passwordSettings.Lowercase,
"uppercase": passwordSettings.Uppercase,
"number": passwordSettings.Number,
"nonAlphanumeric": passwordSettings.NonAlphanumeric,
"expiration": passwordSettings.Expiration,
"expirationWeeks": passwordSettings.ExpirationWeeks,
"reuse": passwordSettings.Reuse,
"reuseAmount": passwordSettings.ReuseAmount,
"lock": passwordSettings.Lock,
"lockAttempts": passwordSettings.LockAttempts,
"emailServiceProvider": passwordSettings.EmailServiceProvider,
"emailSubject": passwordSettings.EmailSubject,
"emailBody": passwordSettings.EmailBody,
"emailBodyPlainText": passwordSettings.EmailBodyPlainText,
"useEmailBodyPlainText": passwordSettings.UseEmailBodyPlainText,
}
_, err := s.client.DoPostRequest(ctx, api.Routes.ManagementPasswordSettings(), req, nil, s.conf.ManagementKey)
return err
}

func unmarshalPasswordSettingsResponse(res *api.HTTPResponse) (*descope.PasswordSettings, error) {
var passwordSettingsRes *descope.PasswordSettings
err := utils.Unmarshal([]byte(res.BodyStr), &passwordSettingsRes)
if err != nil {
return nil, err
}
return passwordSettingsRes, err
}
119 changes: 119 additions & 0 deletions descope/internal/mgmt/password_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package mgmt

import (
"context"
"net/http"
"testing"

"github.com/descope/go-sdk/descope"
"github.com/descope/go-sdk/descope/tests/helpers"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestGetPasswordSettingsSuccess(t *testing.T) {
tenantID := "abc"
response := map[string]any{
"tenantID": tenantID,
"enabled": true,
"lock": true,
"uppercase": true,
"lowercase": true,
"minLength": 8,
"number": true,
}
mgmt := newTestMgmt(nil, helpers.DoOkWithBody(func(r *http.Request) {
require.Equal(t, r.Header.Get("Authorization"), "Bearer a:key")
params := helpers.ReadParams(r)
require.Equal(t, tenantID, params["tenantId"])
}, response))
res, err := mgmt.Password().GetSettings(context.Background(), tenantID)
require.NoError(t, err)
assert.True(t, res.Lock)
assert.True(t, res.Enabled)
assert.True(t, res.Uppercase)
assert.True(t, res.Lowercase)
assert.True(t, res.Number)
assert.EqualValues(t, 8, res.MinLength)
}

func TestGetPasswordSettingsError(t *testing.T) {
tenantID := "abc"
mgmt := newTestMgmt(nil, helpers.DoBadRequest(func(r *http.Request) {
require.Equal(t, r.Header.Get("Authorization"), "Bearer a:key")
params := helpers.ReadParams(r)
require.Equal(t, tenantID, params["tenantId"])
}))
res, err := mgmt.Password().GetSettings(context.Background(), tenantID)
require.Error(t, err)
assert.Nil(t, res)
}

func TestPasswordConfigureSettingsSuccess(t *testing.T) {
mgmt := newTestMgmt(nil, helpers.DoOk(func(r *http.Request) {
require.Equal(t, r.Header.Get("Authorization"), "Bearer a:key")
req := map[string]any{}
require.NoError(t, helpers.ReadBody(r, &req))
require.Equal(t, "tenant", req["tenantId"])
require.Equal(t, true, req["enabled"])
require.Equal(t, float64(8), req["minLength"])
require.Equal(t, true, req["uppercase"])
require.Equal(t, true, req["lowercase"])
require.Equal(t, true, req["number"])
require.Equal(t, true, req["nonAlphanumeric"])
require.Equal(t, true, req["expiration"])
require.Equal(t, float64(3), req["expirationWeeks"])
require.Equal(t, true, req["reuse"])
require.Equal(t, float64(3), req["reuseAmount"])
require.Equal(t, true, req["lock"])
require.Equal(t, float64(4), req["lockAttempts"])
require.Equal(t, "Descope", req["emailServiceProvider"])
require.Equal(t, "subject", req["emailSubject"])
require.Equal(t, "body", req["emailBody"])
require.Equal(t, "text", req["emailBodyPlainText"])
}))
err := mgmt.Password().ConfigureSettings(context.Background(), "tenant", &descope.PasswordSettings{
Enabled: true,
MinLength: 8,
Lowercase: true,
Uppercase: true,
Number: true,
NonAlphanumeric: true,
Expiration: true,
ExpirationWeeks: 3,
Reuse: true,
ReuseAmount: 3,
Lock: true,
LockAttempts: 4,
EmailServiceProvider: "Descope",
EmailSubject: "subject",
EmailBody: "body",
EmailBodyPlainText: "text",
UseEmailBodyPlainText: true,
})
require.NoError(t, err)
}

func TestPasswordConfigureSettingsError(t *testing.T) {
mgmt := newTestMgmt(nil, helpers.DoBadRequest(nil))
err := mgmt.Password().ConfigureSettings(context.Background(), "tenant", &descope.PasswordSettings{
Enabled: true,
MinLength: 8,
Lowercase: true,
Uppercase: true,
Number: true,
NonAlphanumeric: true,
Expiration: true,
ExpirationWeeks: 3,
Reuse: true,
ReuseAmount: 3,
Lock: true,
LockAttempts: 4,
EmailServiceProvider: "Descope",
EmailSubject: "subject",
EmailBody: "body",
EmailBodyPlainText: "text",
UseEmailBodyPlainText: true,
})
require.Error(t, err)
}
48 changes: 48 additions & 0 deletions descope/internal/mgmt/tenant.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,41 @@ func (t *tenant) SearchAll(ctx context.Context, options *descope.TenantSearchOpt
return unmarshalLoadAllTenantsResponse(res)
}

func (t *tenant) GetSettings(ctx context.Context, tenantID string) (*descope.TenantSettings, error) {
if tenantID == "" {
return nil, utils.NewInvalidArgumentError("tenantID")
}
req := &api.HTTPRequest{
QueryParams: map[string]string{"tenantId": tenantID},
}
res, err := t.client.DoGetRequest(ctx, api.Routes.ManagementTenantSettings(), req, t.conf.ManagementKey)
if err != nil {
return nil, err
}
return unmarshalTenantSettingsResponse(res)
}

func (t *tenant) ConfigureSettings(ctx context.Context, tenantID string, settings *descope.TenantSettings) error {
if tenantID == "" {
return utils.NewInvalidArgumentError("tenantID")
}
req := map[string]any{
"tenantId": tenantID,
"selfProvisioningDomains": settings.SelfProvisioningDomains,
"enabled": settings.SessionSettingsEnabled,
"sessionTokenExpiration": settings.SessionTokenExpiration,
"refreshTokenExpiration": settings.RefreshTokenExpiration,
"sessionTokenExpirationUnit": settings.SessionTokenExpirationUnit,
"refreshTokenExpirationUnit": settings.RefreshTokenExpirationUnit,
"inactivityTime": settings.InactivityTime,
"inactivityTimeUnit": settings.InactivityTimeUnit,
"enableInactivity": settings.EnableInactivity,
"domains": settings.Domains,
}
_, err := t.client.DoPostRequest(ctx, api.Routes.ManagementTenantSettings(), req, nil, t.conf.ManagementKey)
return err
}

func makeCreateUpdateTenantRequest(id string, tenantRequest *descope.TenantRequest) map[string]any {
return map[string]any{"id": id, "name": tenantRequest.Name, "selfProvisioningDomains": tenantRequest.SelfProvisioningDomains, "customAttributes": tenantRequest.CustomAttributes}
}
Expand Down Expand Up @@ -138,3 +173,16 @@ func makeSearchTenantRequest(options *descope.TenantSearchOptions) map[string]an
"authType": options.AuthType,
}
}

func unmarshalTenantSettingsResponse(res *api.HTTPResponse) (*descope.TenantSettings, error) {
var tres *struct {
*descope.TenantSettings
Enabled bool `json:"enabled"`
}
err := utils.Unmarshal([]byte(res.BodyStr), &tres)
if err != nil {
return nil, err
}
tres.TenantSettings.SessionSettingsEnabled = tres.Enabled
return tres.TenantSettings, nil
}
Loading