Skip to content

Commit

Permalink
Merge branch 'main' into sign-up-or-in-oauth
Browse files Browse the repository at this point in the history
  • Loading branch information
shilgapira authored Feb 20, 2024
2 parents 19ab677 + c2b8724 commit ef0e714
Show file tree
Hide file tree
Showing 10 changed files with 278 additions and 15 deletions.
23 changes: 23 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = tab
tab_width = 4

[*.sh]
tab_width = 2

[*.md]
trim_trailing_whitespace = false

[*.{yml,yaml,json,html}]
indent_style = space
indent_size = 2

[Dockerfile]

[{*.go,Makefile,.gitmodules,go.mod,go.sum}]
31 changes: 25 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ These sections show how to use the SDK to perform API management functions. Befo
7. [Query SSO Groups](#query-sso-groups)
8. [Manage Flows](#manage-flows)
9. [Manage JWTs](#manage-jwts)
9. [Impersonate](#impersonate)
10. [Search Audit](#search-audit)
11. [Embedded Links](#embedded-links)
12. [Manage ReBAC Authz](#manage-rebac-authz)
Expand Down Expand Up @@ -812,15 +813,21 @@ if err == nil {

#### Set or Expire User Password

You can set or expire a user's password.
Note: When setting a password, it will automatically be set as expired.
The user will not be able log-in using an expired password, and will be required replace it on next login.
You can set a new active password for a user, which they can then use to sign in. You can also set a temporary
password that the user will be forced to change on the next login.

```go
// Set a user's password
err := descopeClient.Management.User().SetPassword(context.Background(), "<login-id>", "<some-password>")
// Set a temporary password for the user which they'll need to replace it on next login
err := descopeClient.Management.User().SetTemporaryPassword(context.Background(), "<login-id>", "<some-password>")

// Or alternatively, expire a user password
// Set an active password for the user which they can use to login
err := descopeClient.Management.User().SetActivePassword(context.Background(), "<login-id>", "<some-password>")
```

For a user that already has a password, you can expire it to require them to change it on the next login.

```go
// Expire the user's active password
err := descopeClient.Management.User().ExpirePassword(context.Background(), "<login-id>")

// Later, if the user is signing in with an expired password, the returned error will be ErrPasswordExpired
Expand Down Expand Up @@ -1128,6 +1135,18 @@ if err != nil {
}
```

### Impersonate

You can impersonate to another user
The impersonator user must have the `impersonation` permission in order for this request to work.
The response would be a refresh JWT of the impersonated user
```go
refreshJWT, err := descopeClient.Management.JWT().Impersonate(context.Background(), "impersonator id", "login id", true)
if err != nil {
// handle error
}
```

### Embedded links

```go
Expand Down
19 changes: 19 additions & 0 deletions descope/api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ var (
userSetSsoApps: "mgmt/user/update/ssoapp/set",
userRemoveSsoApps: "mgmt/user/update/ssoapp/remove",
userSetPassword: "mgmt/user/password/set",
userSetTemporaryPassword: "mgmt/user/password/set/temporary",
userSetActivePassword: "mgmt/user/password/set/active",
userExpirePassword: "mgmt/user/password/expire",
userRemoveAllPasskeys: "mgmt/user/passkeys/delete",
userGetProviderToken: "mgmt/user/provider/token",
Expand All @@ -143,6 +145,7 @@ var (
ssoMapping: "mgmt/sso/mapping",
passwordSettings: "mgmt/password/settings",
updateJWT: "mgmt/jwt/update",
impersonate: "mgmt/impersonate",
permissionCreate: "mgmt/permission/create",
permissionUpdate: "mgmt/permission/update",
permissionDelete: "mgmt/permission/delete",
Expand Down Expand Up @@ -291,6 +294,8 @@ type mgmtEndpoints struct {
userSetRole string
userRemoveRole string
userSetPassword string
userSetTemporaryPassword string
userSetActivePassword string
userExpirePassword string
userRemoveAllPasskeys string
userGetProviderToken string
Expand Down Expand Up @@ -325,6 +330,7 @@ type mgmtEndpoints struct {
ssoSAMLSettingsByMetadata string
ssoOIDCSettings string
updateJWT string
impersonate string

passwordSettings string

Expand Down Expand Up @@ -725,10 +731,19 @@ func (e *endpoints) ManagementUserRemoveSSOApps() string {
return path.Join(e.version, e.mgmt.userRemoveSsoApps)
}

// Deprecated
func (e *endpoints) ManagementUserSetPassword() string {
return path.Join(e.version, e.mgmt.userSetPassword)
}

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

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

func (e *endpoints) ManagementUserExpirePassword() string {
return path.Join(e.version, e.mgmt.userExpirePassword)
}
Expand Down Expand Up @@ -824,6 +839,10 @@ func (e *endpoints) ManagementUpdateJWT() string {
return path.Join(e.version, e.mgmt.updateJWT)
}

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

func (e *endpoints) ManagementGenerateEmbeddedLink() string {
return path.Join(e.version, e.mgmt.userCreateEmbeddedLink)
}
Expand Down
24 changes: 24 additions & 0 deletions descope/internal/mgmt/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,27 @@ func (j *jwt) UpdateJWTWithCustomClaims(ctx context.Context, jwt string, customC
}
return jRes.JWT, nil
}

func (j *jwt) Impersonate(ctx context.Context, impersonatorID string, loginID string, validateConcent bool) (string, error) {
if loginID == "" {
return "", utils.NewInvalidArgumentError("loginID")
}
if impersonatorID == "" {
return "", utils.NewInvalidArgumentError("impersonatorID")
}
req := map[string]any{
"loginId": loginID,
"impersonatorId": impersonatorID,
"validateConsent": validateConcent,
}
res, err := j.client.DoPostRequest(ctx, api.Routes.ManagementImpersonate(), req, nil, j.conf.ManagementKey)
if err != nil {
return "", err
}
jRes := &jwtRes{}
err = utils.Unmarshal([]byte(res.BodyStr), jRes)
if err != nil {
return "", err //notest
}
return jRes.JWT, nil
}
42 changes: 42 additions & 0 deletions descope/internal/mgmt/jwt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,45 @@ func TestUpdateJwtHTTPError(t *testing.T) {
require.True(t, called)
require.Empty(t, jwtRes)
}

func TestImpersonate(t *testing.T) {
impID := "id1"
loginID := "id2"
expectedJWT := "res"
mgmt := newTestMgmt(nil, helpers.DoOkWithBody(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.EqualValues(t, impID, req["impersonatorId"])
require.EqualValues(t, loginID, req["loginId"])
require.EqualValues(t, true, req["validateConsent"])

}, map[string]interface{}{"jwt": expectedJWT}))
jwtRes, err := mgmt.JWT().Impersonate(context.Background(), impID, loginID, true)
require.NoError(t, err)
require.EqualValues(t, expectedJWT, jwtRes)
}

func TestImpersonateMissingLoginID(t *testing.T) {
called := false
mgmt := newTestMgmt(nil, helpers.DoOk(func(r *http.Request) {
called = true

}))
jwtRes, err := mgmt.JWT().Impersonate(context.Background(), "test", "", true)
require.Error(t, err)
require.False(t, called)
require.Empty(t, jwtRes)
}

func TestImpersonateMissingImpersonator(t *testing.T) {
called := false
mgmt := newTestMgmt(nil, helpers.DoOk(func(r *http.Request) {
called = true

}))
jwtRes, err := mgmt.JWT().Impersonate(context.Background(), "", "test", true)
require.Error(t, err)
require.False(t, called)
require.Empty(t, jwtRes)
}
35 changes: 31 additions & 4 deletions descope/internal/mgmt/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,32 @@ func (u *user) RemoveTenantRoles(ctx context.Context, loginID string, tenantID s
return unmarshalUserResponse(res)
}

func (u *user) SetTemporaryPassword(ctx context.Context, loginID string, password string) error {
if loginID == "" {
return utils.NewInvalidArgumentError("loginID")
}
if password == "" {
return utils.NewInvalidArgumentError("password")
}

req := makeSetPasswordRequest(loginID, password, false)
_, err := u.client.DoPostRequest(ctx, api.Routes.ManagementUserSetTemporaryPassword(), req, nil, u.conf.ManagementKey)
return err
}
func (u *user) SetActivePassword(ctx context.Context, loginID string, password string) error {
if loginID == "" {
return utils.NewInvalidArgumentError("loginID")
}
if password == "" {
return utils.NewInvalidArgumentError("password")
}

req := makeSetPasswordRequest(loginID, password, true)
_, err := u.client.DoPostRequest(ctx, api.Routes.ManagementUserSetActivePassword(), req, nil, u.conf.ManagementKey)
return err
}

/* Deprecated */
func (u *user) SetPassword(ctx context.Context, loginID string, password string) error {
if loginID == "" {
return utils.NewInvalidArgumentError("loginID")
Expand All @@ -428,7 +454,7 @@ func (u *user) SetPassword(ctx context.Context, loginID string, password string)
return utils.NewInvalidArgumentError("password")
}

req := makeSetPasswordRequest(loginID, password)
req := makeSetPasswordRequest(loginID, password, false)
_, err := u.client.DoPostRequest(ctx, api.Routes.ManagementUserSetPassword(), req, nil, u.conf.ManagementKey)
return err
}
Expand Down Expand Up @@ -676,10 +702,11 @@ func makeUpdateUserSSOAppsRequest(loginID string, ssoAppIDs []string) map[string
}
}

func makeSetPasswordRequest(loginID string, password string) map[string]any {
func makeSetPasswordRequest(loginID string, password string, setActive bool) map[string]any {
return map[string]any{
"loginId": loginID,
"password": password,
"loginId": loginID,
"password": password,
"setActive": setActive,
}
}

Expand Down
57 changes: 57 additions & 0 deletions descope/internal/mgmt/user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1189,6 +1189,62 @@ func TestUserRemoveTenantRoleError(t *testing.T) {
require.Nil(t, res)
}

func TestUserSetTemporaryPasswordSuccess(t *testing.T) {
response := map[string]any{}
m := newTestMgmt(nil, helpers.DoOkWithBody(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, "abc", req["loginId"])
require.Equal(t, "123", req["password"])
require.False(t, req["setActive"].(bool))
}, response))
err := m.User().SetTemporaryPassword(context.Background(), "abc", "123")
require.NoError(t, err)
}

func TestUserSetTemporaryPasswordBadInput(t *testing.T) {
m := newTestMgmt(nil, helpers.DoOk(nil))
err := m.User().SetTemporaryPassword(context.Background(), "", "123")
require.Error(t, err)
err = m.User().SetTemporaryPassword(context.Background(), "abc", "")
require.Error(t, err)
}

func TestUserSetTemporaryPasswordError(t *testing.T) {
m := newTestMgmt(nil, helpers.DoBadRequest(nil))
err := m.User().SetTemporaryPassword(context.Background(), "abc", "123")
require.Error(t, err)
}

func TestUserSetActivePasswordSuccess(t *testing.T) {
response := map[string]any{}
m := newTestMgmt(nil, helpers.DoOkWithBody(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, "abc", req["loginId"])
require.Equal(t, "123", req["password"])
require.True(t, req["setActive"].(bool))
}, response))
err := m.User().SetActivePassword(context.Background(), "abc", "123")
require.NoError(t, err)
}

func TestUserSetActivePasswordBadInput(t *testing.T) {
m := newTestMgmt(nil, helpers.DoOk(nil))
err := m.User().SetActivePassword(context.Background(), "", "123")
require.Error(t, err)
err = m.User().SetActivePassword(context.Background(), "abc", "")
require.Error(t, err)
}

func TestUserSetActivePasswordError(t *testing.T) {
m := newTestMgmt(nil, helpers.DoBadRequest(nil))
err := m.User().SetActivePassword(context.Background(), "abc", "123")
require.Error(t, err)
}

func TestUserSetPasswordSuccess(t *testing.T) {
response := map[string]any{}
m := newTestMgmt(nil, helpers.DoOkWithBody(func(r *http.Request) {
Expand All @@ -1197,6 +1253,7 @@ func TestUserSetPasswordSuccess(t *testing.T) {
require.NoError(t, helpers.ReadBody(r, &req))
require.Equal(t, "abc", req["loginId"])
require.Equal(t, "123", req["password"])
require.False(t, req["setActive"].(bool))
}, response))
err := m.User().SetPassword(context.Background(), "abc", "123")
require.NoError(t, err)
Expand Down
16 changes: 16 additions & 0 deletions descope/sdk/mgmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -315,10 +315,21 @@ type User interface {
// Remove roles from a user in a specific tenant.
RemoveTenantRoles(ctx context.Context, loginID string, tenantID string, roles []string) (*descope.UserResponse, error)

// Set a temporary password for the given login ID.
// Note: The password will automatically be set as expired.
// The user will not be able to log-in with this password, and will be required to replace it on next login.
// See also: ExpirePassword
SetTemporaryPassword(ctx context.Context, loginID string, password string) error

// Set a password for the given login ID.
// The password will not be expired on the next login
SetActivePassword(ctx context.Context, loginID string, password string) error

// Set a password for the given login ID.
// Note: The password will automatically be set as expired.
// The user will not be able to log-in with this password, and will be required to replace it on next login.
// See also: ExpirePassword
// Deprecated: Use SetTemporaryPassword instead
SetPassword(ctx context.Context, loginID string, password string) error

// Expire the password for the given login ID.
Expand Down Expand Up @@ -509,6 +520,11 @@ type JWT interface {
// Update a valid JWT with the custom claims provided
// The new JWT will be returned
UpdateJWTWithCustomClaims(ctx context.Context, jwt string, customClaims map[string]any) (string, error)

// Impersonate to another user
// The impersonator user must have `impersonation` permission in order for this request to work
// The response would be a refresh JWT of the impersonated user
Impersonate(ctx context.Context, impersonatorID string, loginID string, validateConcent bool) (string, error)
}

// Provides functions for managing permissions in a project.
Expand Down
Loading

0 comments on commit ef0e714

Please sign in to comment.