Merge pull request #573 from openziti/permission_model

Permission Model: Phase 1 (#432)
This commit is contained in:
Michael Quigley 2024-03-08 15:48:14 -05:00 committed by GitHub
commit f1c9f11e4d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 739 additions and 22 deletions

View File

@ -2,6 +2,8 @@
## v0.4.26
FEATURE: New _permission modes_ available for shares. _Open permission mode_ retains the behavior of previous zrok releases and is the default setting. _Closed permission mode_ (`--closed`) only allows a share to be accessed (`zrok access`) by users who have been granted access with the `--access-grant` flag. See the documentation at (https://docs.zrok.io/docs/guides/permission-modes/) (https://github.com/openziti/zrok/issues/432)
CHANGE: The target for a `socks` share is automatically set to `socks` to improve web console display.
CHANGE: Enhancements to the look and feel of the account actions tab in the web console. Textual improvements.

View File

@ -26,6 +26,7 @@ func init() {
testCmd.AddCommand(loopCmd)
rootCmd.AddCommand(adminCmd)
rootCmd.AddCommand(configCmd)
rootCmd.AddCommand(modifyCmd)
rootCmd.AddCommand(shareCmd)
rootCmd.AddCommand(testCmd)
transport.AddAddressParser(tcp.AddressParser{})
@ -85,6 +86,12 @@ var loopCmd = &cobra.Command{
Short: "Loopback testing utilities",
}
var modifyCmd = &cobra.Command{
Use: "modify",
Aliases: []string{"mod"},
Short: "Modify resources",
}
var shareCmd = &cobra.Command{
Use: "share",
Short: "Create backend access for shares",

75
cmd/zrok/modifyShare.go Normal file
View File

@ -0,0 +1,75 @@
package main
import (
"fmt"
httptransport "github.com/go-openapi/runtime/client"
"github.com/openziti/zrok/environment"
"github.com/openziti/zrok/rest_client_zrok/share"
"github.com/openziti/zrok/rest_model_zrok"
"github.com/openziti/zrok/tui"
"github.com/spf13/cobra"
)
func init() {
modifyCmd.AddCommand(newModifyShareCommand().cmd)
}
type modifyShareCommand struct {
addAccessGrants []string
removeAccessGrants []string
cmd *cobra.Command
}
func newModifyShareCommand() *modifyShareCommand {
cmd := &cobra.Command{
Use: "share <shareToken>",
Args: cobra.ExactArgs(1),
Short: "Modify a share",
}
command := &modifyShareCommand{cmd: cmd}
cmd.Flags().StringArrayVar(&command.addAccessGrants, "add-access-grant", []string{}, "Add an access grant (email address)")
cmd.Flags().StringArrayVar(&command.removeAccessGrants, "remove-access-grant", []string{}, "Remove an access grant (email address)")
cmd.Run = command.run
return command
}
func (cmd *modifyShareCommand) run(_ *cobra.Command, args []string) {
shrToken := args[0]
root, err := environment.LoadRoot()
if err != nil {
if !panicInstead {
tui.Error("error loading environment", err)
}
panic(err)
}
if !root.IsEnabled() {
tui.Error("unable to load environment; did you 'zrok enable'?", nil)
}
zrok, err := root.Client()
if err != nil {
if !panicInstead {
tui.Error("unable to create zrok client", err)
}
panic(err)
}
auth := httptransport.APIKeyAuth("X-TOKEN", "header", root.Environment().Token)
if len(cmd.addAccessGrants) > 0 || len(cmd.removeAccessGrants) > 0 {
req := share.NewUpdateShareParams()
req.Body = &rest_model_zrok.UpdateShareRequest{
ShrToken: shrToken,
AddAccessGrants: cmd.addAccessGrants,
RemoveAccessGrants: cmd.removeAccessGrants,
}
if _, err := zrok.Share.UpdateShare(req, auth); err != nil {
if !panicInstead {
tui.Error("unable to update share", err)
}
panic(err)
}
fmt.Println("updated")
}
}

View File

@ -26,6 +26,8 @@ type reserveCommand struct {
oauthProvider string
oauthEmailAddressPatterns []string
oauthCheckInterval time.Duration
closed bool
accessGrants []string
cmd *cobra.Command
}
@ -45,6 +47,8 @@ func newReserveCommand() *reserveCommand {
cmd.Flags().StringArrayVar(&command.oauthEmailAddressPatterns, "oauth-email-address-patterns", []string{}, "Allow only these email domains to authenticate via OAuth")
cmd.Flags().DurationVar(&command.oauthCheckInterval, "oauth-check-interval", 3*time.Hour, "Maximum lifetime for OAuth authentication; reauthenticate after expiry")
cmd.MarkFlagsMutuallyExclusive("basic-auth", "oauth-provider")
cmd.Flags().BoolVar(&command.closed, "closed", false, "Enable closed permission mode (see --access-grant)")
cmd.Flags().StringArrayVar(&command.accessGrants, "access-grant", []string{}, "zrok accounts that are allowed to access this share (see --closed)")
cmd.Run = command.run
return command
@ -142,6 +146,10 @@ func (cmd *reserveCommand) run(_ *cobra.Command, args []string) {
req.OauthEmailAddressPatterns = cmd.oauthEmailAddressPatterns
req.OauthAuthorizationCheckInterval = cmd.oauthCheckInterval
}
if cmd.closed {
req.PermissionMode = sdk.ClosedPermissionMode
req.AccessGrants = cmd.accessGrants
}
shr, err := sdk.CreateShare(env, req)
if err != nil {
tui.Error("unable to create share", err)

View File

@ -25,11 +25,13 @@ func init() {
}
type sharePrivateCommand struct {
basicAuth []string
backendMode string
headless bool
insecure bool
cmd *cobra.Command
basicAuth []string
backendMode string
headless bool
insecure bool
closed bool
accessGrants []string
cmd *cobra.Command
}
func newSharePrivateCommand() *sharePrivateCommand {
@ -43,6 +45,8 @@ func newSharePrivateCommand() *sharePrivateCommand {
cmd.Flags().StringVarP(&command.backendMode, "backend-mode", "b", "proxy", "The backend mode {proxy, web, tcpTunnel, udpTunnel, caddy, drive, socks}")
cmd.Flags().BoolVar(&command.headless, "headless", false, "Disable TUI and run headless")
cmd.Flags().BoolVar(&command.insecure, "insecure", false, "Enable insecure TLS certificate validation for <target>")
cmd.Flags().BoolVar(&command.closed, "closed", false, "Enable closed permission mode (see --access-grant)")
cmd.Flags().StringArrayVar(&command.accessGrants, "access-grant", []string{}, "zrok accounts that are allowed to access this share (see --closed)")
cmd.Run = command.run
return command
}
@ -131,6 +135,10 @@ func (cmd *sharePrivateCommand) run(_ *cobra.Command, args []string) {
BasicAuth: cmd.basicAuth,
Target: target,
}
if cmd.closed {
req.PermissionMode = sdk.ClosedPermissionMode
req.AccessGrants = cmd.accessGrants
}
shr, err := sdk.CreateShare(root, req)
if err != nil {
if !panicInstead {

View File

@ -33,6 +33,8 @@ type sharePublicCommand struct {
oauthProvider string
oauthEmailAddressPatterns []string
oauthCheckInterval time.Duration
closed bool
accessGrants []string
cmd *cobra.Command
}
@ -47,6 +49,8 @@ func newSharePublicCommand() *sharePublicCommand {
cmd.Flags().StringVarP(&command.backendMode, "backend-mode", "b", "proxy", "The backend mode {proxy, web, caddy, drive}")
cmd.Flags().BoolVar(&command.headless, "headless", false, "Disable TUI and run headless")
cmd.Flags().BoolVar(&command.insecure, "insecure", false, "Enable insecure TLS certificate validation for <target>")
cmd.Flags().BoolVar(&command.closed, "closed", false, "Enable closed permission mode (see --access-grant)")
cmd.Flags().StringArrayVar(&command.accessGrants, "access-grant", []string{}, "zrok accounts that are allowed to access this share (see --closed)")
cmd.Flags().StringArrayVar(&command.basicAuth, "basic-auth", []string{}, "Basic authentication users (<username:password>,...)")
cmd.Flags().StringVar(&command.oauthProvider, "oauth-provider", "", "Enable OAuth provider [google, github]")
@ -113,6 +117,10 @@ func (cmd *sharePublicCommand) run(_ *cobra.Command, args []string) {
BasicAuth: cmd.basicAuth,
Target: target,
}
if cmd.closed {
req.PermissionMode = sdk.ClosedPermissionMode
req.AccessGrants = cmd.accessGrants
}
if cmd.oauthProvider != "" {
req.OauthProvider = cmd.oauthProvider
req.OauthEmailAddressPatterns = cmd.oauthEmailAddressPatterns

View File

@ -49,7 +49,7 @@ func (h *accessHandler) Handle(params share.AccessParams, principal *rest_model_
shrToken := params.Body.ShrToken
shr, err := str.FindShareWithToken(shrToken, trx)
if err != nil {
logrus.Errorf("error finding share")
logrus.Errorf("error finding share with token '%v': %v", shrToken, err)
return share.NewAccessNotFound()
}
if shr == nil {
@ -57,6 +57,19 @@ func (h *accessHandler) Handle(params share.AccessParams, principal *rest_model_
return share.NewAccessNotFound()
}
if shr.PermissionMode == store.ClosedPermissionMode {
shrEnv, err := str.GetEnvironment(shr.EnvironmentId, trx)
if err != nil {
logrus.Errorf("error getting environment for share '%v': %v", shrToken, err)
return share.NewAccessInternalServerError()
}
if err := h.checkAccessGrants(shr, *shrEnv.AccountId, principal, trx); err != nil {
logrus.Errorf("closed permission mode for '%v' fails for '%v': %v", shr.Token, principal.Email, err)
return share.NewAccessUnauthorized()
}
}
if err := h.checkLimits(shr, trx); err != nil {
logrus.Errorf("cannot access limited share for '%v': %v", principal.Email, err)
return share.NewAccessNotFound()
@ -111,3 +124,20 @@ func (h *accessHandler) checkLimits(shr *store.Share, trx *sqlx.Tx) error {
}
return nil
}
func (h *accessHandler) checkAccessGrants(shr *store.Share, ownerAccountId int, principal *rest_model_zrok.Principal, trx *sqlx.Tx) error {
if int(principal.ID) == ownerAccountId {
logrus.Infof("accessing own share '%v' for '%v'", shr.Token, principal.Email)
return nil
}
count, err := str.CheckAccessGrantForShareAndAccount(shr.Id, int(principal.ID), trx)
if err != nil {
logrus.Infof("error checking access grants for '%v': %v", shr.Token, err)
return err
}
if count > 0 {
logrus.Infof("found '%d' grants for '%v'", count, principal.Email)
return nil
}
return errors.Errorf("access denied for '%v' accessing '%v'", principal.Email, shr.Token)
}

View File

@ -54,6 +54,19 @@ func (h *shareHandler) Handle(params share.ShareParams, principal *rest_model_zr
return share.NewShareUnauthorized()
}
var accessGrantAcctIds []int
if store.PermissionMode(params.Body.PermissionMode) == store.ClosedPermissionMode {
for _, email := range params.Body.AccessGrants {
acct, err := str.FindAccountWithEmail(email, trx)
if err != nil {
logrus.Errorf("unable to find account '%v' for share request from '%v'", email, principal.Email)
return share.NewShareNotFound()
}
logrus.Debugf("found id '%d' for '%v'", acct.Id, acct.Email)
accessGrantAcctIds = append(accessGrantAcctIds, acct.Id)
}
}
edge, err := zrokEdgeSdk.Client(cfg.Ziti)
if err != nil {
logrus.Error(err)
@ -134,6 +147,10 @@ func (h *shareHandler) Handle(params share.ShareParams, principal *rest_model_zr
BackendMode: params.Body.BackendMode,
BackendProxyEndpoint: &params.Body.BackendProxyEndpoint,
Reserved: reserved,
PermissionMode: store.OpenPermissionMode,
}
if params.Body.PermissionMode != "" {
sshr.PermissionMode = store.PermissionMode(params.Body.PermissionMode)
}
if len(params.Body.FrontendSelection) > 0 {
sshr.FrontendSelection = &params.Body.FrontendSelection[0]
@ -150,6 +167,16 @@ func (h *shareHandler) Handle(params share.ShareParams, principal *rest_model_zr
return share.NewShareInternalServerError()
}
if sshr.PermissionMode == store.ClosedPermissionMode {
for _, acctId := range accessGrantAcctIds {
_, err := str.CreateAccessGrant(sid, acctId, trx)
if err != nil {
logrus.Errorf("error creating access grant for '%v': %v", principal.Email, err)
return share.NewShareInternalServerError()
}
}
}
if err := trx.Commit(); err != nil {
logrus.Errorf("error committing share record: %v", err)
return share.NewShareInternalServerError()

View File

@ -0,0 +1,57 @@
package store
import (
"github.com/jmoiron/sqlx"
"github.com/pkg/errors"
)
type AccessGrant struct {
Model
ShareId int
AccountId int
}
func (str *Store) CreateAccessGrant(shareId, accountId int, tx *sqlx.Tx) (int, error) {
stmt, err := tx.Prepare("insert into access_grants (share_id, account_id) values ($1, $2) returning id")
if err != nil {
return 0, errors.Wrap(err, "error preparing access_grant insert statement")
}
var id int
if err := stmt.QueryRow(shareId, accountId).Scan(&id); err != nil {
return 0, errors.Wrap(err, "error executing access_grant insert statement")
}
return id, nil
}
func (str *Store) CheckAccessGrantForShareAndAccount(shrId, acctId int, tx *sqlx.Tx) (int, error) {
count := 0
err := tx.QueryRowx("select count(0) from access_grants where share_id = $1 and account_id = $2 and not deleted", shrId, acctId).Scan(&count)
if err != nil {
return 0, errors.Wrap(err, "error selecting access_grants by share_id and account_id")
}
return count, nil
}
func (str *Store) DeleteAccessGrantsForShare(shrId int, tx *sqlx.Tx) error {
stmt, err := tx.Prepare("update access_grants set updated_at = current_timestamp, deleted = true where share_id = $1")
if err != nil {
return errors.Wrap(err, "error preparing access_grants delete for shares statement")
}
_, err = stmt.Exec(shrId)
if err != nil {
return errors.Wrap(err, "error executing access_grants delete for shares statement")
}
return nil
}
func (str *Store) DeleteAccessGrantsForShareAndAccount(shrId, acctId int, tx *sqlx.Tx) error {
stmt, err := tx.Prepare("update access_grants set updated_at = current_timestamp, deleted = true where share_id = $1 and account_id = $2")
if err != nil {
return errors.Wrap(err, "error preparing access_grants delete for share and account statement")
}
_, err = stmt.Exec(shrId, acctId)
if err != nil {
return errors.Wrap(err, "error executing access_grants delete for share and account statement")
}
return nil
}

View File

@ -7,3 +7,10 @@ const (
WarningAction LimitJournalAction = "warning"
ClearAction LimitJournalAction = "clear"
)
type PermissionMode string
const (
OpenPermissionMode PermissionMode = "open"
ClosedPermissionMode PermissionMode = "closed"
)

View File

@ -16,16 +16,17 @@ type Share struct {
FrontendEndpoint *string
BackendProxyEndpoint *string
Reserved bool
PermissionMode PermissionMode
Deleted bool
}
func (str *Store) CreateShare(envId int, shr *Share, tx *sqlx.Tx) (int, error) {
stmt, err := tx.Prepare("insert into shares (environment_id, z_id, token, share_mode, backend_mode, frontend_selection, frontend_endpoint, backend_proxy_endpoint, reserved) values ($1, $2, $3, $4, $5, $6, $7, $8, $9) returning id")
stmt, err := tx.Prepare("insert into shares (environment_id, z_id, token, share_mode, backend_mode, frontend_selection, frontend_endpoint, backend_proxy_endpoint, reserved, permission_mode) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) returning id")
if err != nil {
return 0, errors.Wrap(err, "error preparing shares insert statement")
}
var id int
if err := stmt.QueryRow(envId, shr.ZId, shr.Token, shr.ShareMode, shr.BackendMode, shr.FrontendSelection, shr.FrontendEndpoint, shr.BackendProxyEndpoint, shr.Reserved).Scan(&id); err != nil {
if err := stmt.QueryRow(envId, shr.ZId, shr.Token, shr.ShareMode, shr.BackendMode, shr.FrontendSelection, shr.FrontendEndpoint, shr.BackendProxyEndpoint, shr.Reserved, shr.PermissionMode).Scan(&id); err != nil {
return 0, errors.Wrap(err, "error executing shares insert statement")
}
return id, nil
@ -96,12 +97,12 @@ func (str *Store) FindSharesForEnvironment(envId int, tx *sqlx.Tx) ([]*Share, er
}
func (str *Store) UpdateShare(shr *Share, tx *sqlx.Tx) error {
sql := "update shares set z_id = $1, token = $2, share_mode = $3, backend_mode = $4, frontend_selection = $5, frontend_endpoint = $6, backend_proxy_endpoint = $7, reserved = $8, updated_at = current_timestamp where id = $9"
sql := "update shares set z_id = $1, token = $2, share_mode = $3, backend_mode = $4, frontend_selection = $5, frontend_endpoint = $6, backend_proxy_endpoint = $7, reserved = $8, permission_mode = $9, updated_at = current_timestamp where id = $10"
stmt, err := tx.Prepare(sql)
if err != nil {
return errors.Wrap(err, "error preparing shares update statement")
}
_, err = stmt.Exec(shr.ZId, shr.Token, shr.ShareMode, shr.BackendMode, shr.FrontendSelection, shr.FrontendEndpoint, shr.BackendProxyEndpoint, shr.Reserved, shr.Id)
_, err = stmt.Exec(shr.ZId, shr.Token, shr.ShareMode, shr.BackendMode, shr.FrontendSelection, shr.FrontendEndpoint, shr.BackendProxyEndpoint, shr.Reserved, shr.PermissionMode, shr.Id)
if err != nil {
return errors.Wrap(err, "error executing shares update statement")
}

View File

@ -0,0 +1,14 @@
-- +migrate Up
create type permission_mode_type as enum('open', 'closed');
alter table shares add column permission_mode permission_mode_type not null default('open');
create table access_grants (
id serial primary key,
share_id integer references shares(id),
account_id integer references accounts(id),
created_at timestamptz not null default(current_timestamp),
updated_at timestamptz not null default(current_timestamp),
deleted boolean not null default(false)
);

View File

@ -0,0 +1,12 @@
-- +migrate Up
alter table shares add column permission_mode string not null default('open');
create table access_grants (
id integer primary key,
share_id integer references shares(id),
account_id integer references accounts(id),
created_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')),
updated_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')),
deleted boolean not null default(false)
);

View File

@ -79,8 +79,12 @@ func (h *unshareHandler) Handle(params share.UnshareParams, principal *rest_mode
h.deallocateResources(senv, shrToken, shrZId, edge)
logrus.Debugf("deallocated share '%v'", shrToken)
if err := str.DeleteAccessGrantsForShare(sshr.Id, tx); err != nil {
logrus.Errorf("error deleting access grants for share '%v': %v", shrToken, err)
return share.NewUnshareInternalServerError()
}
if err := str.DeleteShare(sshr.Id, tx); err != nil {
logrus.Errorf("error deactivating share '%v': %v", shrZId, err)
logrus.Errorf("error deleting share '%v': %v", shrToken, err)
return share.NewUnshareInternalServerError()
}
if err := tx.Commit(); err != nil {

View File

@ -48,15 +48,49 @@ func (h *updateShareHandler) Handle(params share.UpdateShareParams, principal *r
return share.NewUpdateShareNotFound()
}
sshr.BackendProxyEndpoint = &backendProxyEndpoint
if err := str.UpdateShare(sshr, tx); err != nil {
logrus.Errorf("error updating share '%v': %v", shrToken, err)
return share.NewUpdateShareInternalServerError()
doCommit := false
if backendProxyEndpoint != "" {
sshr.BackendProxyEndpoint = &backendProxyEndpoint
if err := str.UpdateShare(sshr, tx); err != nil {
logrus.Errorf("error updating share '%v': %v", shrToken, err)
return share.NewUpdateShareInternalServerError()
}
doCommit = true
}
if err := tx.Commit(); err != nil {
logrus.Errorf("error committing transaction for share '%v' update: %v", shrToken, err)
return share.NewUpdateShareInternalServerError()
for _, addr := range params.Body.AddAccessGrants {
acct, err := str.FindAccountWithEmail(addr, tx)
if err != nil {
logrus.Errorf("error looking up account by email '%v' for user '%v': %v", addr, principal.Email, err)
return share.NewUpdateShareBadRequest()
}
if _, err := str.CreateAccessGrant(sshr.Id, acct.Id, tx); err != nil {
logrus.Errorf("error adding access grant '%v' for share '%v': %v", acct.Email, shrToken, err)
return share.NewUpdateShareInternalServerError()
}
logrus.Infof("added access grant '%v' to share '%v'", acct.Email, shrToken)
doCommit = true
}
for _, addr := range params.Body.RemoveAccessGrants {
acct, err := str.FindAccountWithEmail(addr, tx)
if err != nil {
logrus.Errorf("error looking up account by email '%v' for user '%v': %v", addr, principal.Email, err)
return share.NewUpdateShareBadRequest()
}
if err := str.DeleteAccessGrantsForShareAndAccount(sshr.Id, acct.Id, tx); err != nil {
logrus.Errorf("error removing access grant '%v' for share '%v': %v", acct.Email, shrToken, err)
return share.NewUpdateShareInternalServerError()
}
logrus.Infof("removed access grant '%v' from share '%v'", acct.Email, shrToken)
doCommit = true
}
if doCommit {
if err := tx.Commit(); err != nil {
logrus.Errorf("error committing transaction for share '%v' update: %v", shrToken, err)
return share.NewUpdateShareInternalServerError()
}
}
return share.NewUpdateShareOK()

View File

@ -0,0 +1,77 @@
---
sidebar_position: 22
sidebar_label: Permission Modes
---
# Permission Modes
Shares created in zrok `v0.4.26` and newer now include a choice of _permission mode_.
Shares created with zrok `v0.4.25` and older were created using what is now called the _open permission mode_. Whether _public_ or _private_, these shares can be accessed by any user of the zrok service instance, as long as they know the _share token_ of the share. Effectively shares with the _open permission mode_ are accessible by any user of the zrok service instance.
zrok now supports a _closed permission mode_, which allows for more fine-grained control over which zrok users are allowed to privately access your shares using `zrok access private`.
zrok defaults to continuing to create shares with the _open permission mode_. This will likely change in a future release. We're leaving the default behavior in place to allow users a period of time to get comfortable with the new permission modes.
## Creating a Share with Closed Permission Mode
Adding the `--closed` flag to the `zrok share` or `zrok reserve` commands will create shares using the _closed permission mode_:
```
$ zrok share private --headless --closed -b web .
[ 0.066] INFO main.(*sharePrivateCommand).run: allow other to access your share with the following command:
zrok access private 0vzwzodf0c7g
```
By default any environment owned by the account that created the share is _allowed_ to access the new share. But a user trying to access the share from an environment owned by a different account will enounter the following error message:
```
$ zrok access private 0vzwzodf0c7g
[ERROR]: unable to access ([POST /access][401] accessUnauthorized)
```
The `zrok share` and `zrok reserve` commands now include an `--access-grant` flag, which allows you to specify additional zrok accounts that are allowed to access your shares:
```
$ zrok share private --headless --closed --access-grant anotheruser@test.com -b web .
[ 0.062] INFO main.(*sharePrivateCommand).run: allow other to access your share with the following command:
zrok access private y6h4at5xvn6o
```
And now `anotheruser@test.com` will be allowed to access the share:
```
$ zrok access private --headless y6h4at5xvn6o
[ 0.049] INFO main.(*accessPrivateCommand).run: allocated frontend 'VyvrJihAOEHD'
[ 0.051] INFO main.(*accessPrivateCommand).run: access the zrok share at the following endpoint: http://127.0.0.1:9191
```
## Adding and Removing Access Grants for Existing Shares
If you've created a share (either reserved or ephemeral) and you forgot to include an access grant, or want to remove an access grant that was mistakenly added, you can use the `zrok modify share` command to make the adjustments:
Create a share:
```
$ zrok share private --headless --closed -b web .
[ 0.064] INFO main.(*sharePrivateCommand).run: allow other to access your share with the following command:
zrok access private s4czjylwk7wa
```
In another shell in the same environment you can execute:
```
$ zrok modify share s4czjylwk7wa --add-access-grant anotheruser@test.com
updated
```
And to remove the grant:
```
$ zrok modify share s4czjylwk7wa --remove-access-grant anotheruser@test.com
updated
```
## Limitations
As of `v0.4.26` there is currently no way to _list_ the current access grants. This will be addressed shortly in a subsequent update.

View File

@ -26,6 +26,12 @@ func (o *UpdateShareReader) ReadResponse(response runtime.ClientResponse, consum
return nil, err
}
return result, nil
case 400:
result := NewUpdateShareBadRequest()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return nil, result
case 401:
result := NewUpdateShareUnauthorized()
if err := result.readResponse(response, consumer, o.formats); err != nil {
@ -105,6 +111,62 @@ func (o *UpdateShareOK) readResponse(response runtime.ClientResponse, consumer r
return nil
}
// NewUpdateShareBadRequest creates a UpdateShareBadRequest with default headers values
func NewUpdateShareBadRequest() *UpdateShareBadRequest {
return &UpdateShareBadRequest{}
}
/*
UpdateShareBadRequest describes a response with status code 400, with default header values.
bad request
*/
type UpdateShareBadRequest struct {
}
// IsSuccess returns true when this update share bad request response has a 2xx status code
func (o *UpdateShareBadRequest) IsSuccess() bool {
return false
}
// IsRedirect returns true when this update share bad request response has a 3xx status code
func (o *UpdateShareBadRequest) IsRedirect() bool {
return false
}
// IsClientError returns true when this update share bad request response has a 4xx status code
func (o *UpdateShareBadRequest) IsClientError() bool {
return true
}
// IsServerError returns true when this update share bad request response has a 5xx status code
func (o *UpdateShareBadRequest) IsServerError() bool {
return false
}
// IsCode returns true when this update share bad request response a status code equal to that given
func (o *UpdateShareBadRequest) IsCode(code int) bool {
return code == 400
}
// Code gets the status code for the update share bad request response
func (o *UpdateShareBadRequest) Code() int {
return 400
}
func (o *UpdateShareBadRequest) Error() string {
return fmt.Sprintf("[PATCH /share][%d] updateShareBadRequest ", 400)
}
func (o *UpdateShareBadRequest) String() string {
return fmt.Sprintf("[PATCH /share][%d] updateShareBadRequest ", 400)
}
func (o *UpdateShareBadRequest) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
return nil
}
// NewUpdateShareUnauthorized creates a UpdateShareUnauthorized with default headers values
func NewUpdateShareUnauthorized() *UpdateShareUnauthorized {
return &UpdateShareUnauthorized{}

View File

@ -21,6 +21,9 @@ import (
// swagger:model shareRequest
type ShareRequest struct {
// access grants
AccessGrants []string `json:"accessGrants"`
// auth scheme
AuthScheme string `json:"authScheme,omitempty"`
@ -50,6 +53,10 @@ type ShareRequest struct {
// Enum: [github google]
OauthProvider string `json:"oauthProvider,omitempty"`
// permission mode
// Enum: [open closed]
PermissionMode string `json:"permissionMode,omitempty"`
// reserved
Reserved bool `json:"reserved,omitempty"`
@ -77,6 +84,10 @@ func (m *ShareRequest) Validate(formats strfmt.Registry) error {
res = append(res, err)
}
if err := m.validatePermissionMode(formats); err != nil {
res = append(res, err)
}
if err := m.validateShareMode(formats); err != nil {
res = append(res, err)
}
@ -212,6 +223,48 @@ func (m *ShareRequest) validateOauthProvider(formats strfmt.Registry) error {
return nil
}
var shareRequestTypePermissionModePropEnum []interface{}
func init() {
var res []string
if err := json.Unmarshal([]byte(`["open","closed"]`), &res); err != nil {
panic(err)
}
for _, v := range res {
shareRequestTypePermissionModePropEnum = append(shareRequestTypePermissionModePropEnum, v)
}
}
const (
// ShareRequestPermissionModeOpen captures enum value "open"
ShareRequestPermissionModeOpen string = "open"
// ShareRequestPermissionModeClosed captures enum value "closed"
ShareRequestPermissionModeClosed string = "closed"
)
// prop value enum
func (m *ShareRequest) validatePermissionModeEnum(path, location string, value string) error {
if err := validate.EnumCase(path, location, value, shareRequestTypePermissionModePropEnum, true); err != nil {
return err
}
return nil
}
func (m *ShareRequest) validatePermissionMode(formats strfmt.Registry) error {
if swag.IsZero(m.PermissionMode) { // not required
return nil
}
// value enum
if err := m.validatePermissionModeEnum("permissionMode", "body", m.PermissionMode); err != nil {
return err
}
return nil
}
var shareRequestTypeShareModePropEnum []interface{}
func init() {

View File

@ -17,9 +17,15 @@ import (
// swagger:model updateShareRequest
type UpdateShareRequest struct {
// add access grants
AddAccessGrants []string `json:"addAccessGrants"`
// backend proxy endpoint
BackendProxyEndpoint string `json:"backendProxyEndpoint,omitempty"`
// remove access grants
RemoveAccessGrants []string `json:"removeAccessGrants"`
// shr token
ShrToken string `json:"shrToken,omitempty"`
}

View File

@ -988,6 +988,9 @@ func init() {
"200": {
"description": "share updated"
},
"400": {
"description": "bad request"
},
"401": {
"description": "unauthorized"
},
@ -1562,6 +1565,12 @@ func init() {
"shareRequest": {
"type": "object",
"properties": {
"accessGrants": {
"type": "array",
"items": {
"type": "string"
}
},
"authScheme": {
"type": "string"
},
@ -1611,6 +1620,13 @@ func init() {
"google"
]
},
"permissionMode": {
"type": "string",
"enum": [
"open",
"closed"
]
},
"reserved": {
"type": "boolean"
},
@ -1708,9 +1724,21 @@ func init() {
"updateShareRequest": {
"type": "object",
"properties": {
"addAccessGrants": {
"type": "array",
"items": {
"type": "string"
}
},
"backendProxyEndpoint": {
"type": "string"
},
"removeAccessGrants": {
"type": "array",
"items": {
"type": "string"
}
},
"shrToken": {
"type": "string"
}
@ -2715,6 +2743,9 @@ func init() {
"200": {
"description": "share updated"
},
"400": {
"description": "bad request"
},
"401": {
"description": "unauthorized"
},
@ -3289,6 +3320,12 @@ func init() {
"shareRequest": {
"type": "object",
"properties": {
"accessGrants": {
"type": "array",
"items": {
"type": "string"
}
},
"authScheme": {
"type": "string"
},
@ -3338,6 +3375,13 @@ func init() {
"google"
]
},
"permissionMode": {
"type": "string",
"enum": [
"open",
"closed"
]
},
"reserved": {
"type": "boolean"
},
@ -3435,9 +3479,21 @@ func init() {
"updateShareRequest": {
"type": "object",
"properties": {
"addAccessGrants": {
"type": "array",
"items": {
"type": "string"
}
},
"backendProxyEndpoint": {
"type": "string"
},
"removeAccessGrants": {
"type": "array",
"items": {
"type": "string"
}
},
"shrToken": {
"type": "string"
}

View File

@ -36,6 +36,31 @@ func (o *UpdateShareOK) WriteResponse(rw http.ResponseWriter, producer runtime.P
rw.WriteHeader(200)
}
// UpdateShareBadRequestCode is the HTTP code returned for type UpdateShareBadRequest
const UpdateShareBadRequestCode int = 400
/*
UpdateShareBadRequest bad request
swagger:response updateShareBadRequest
*/
type UpdateShareBadRequest struct {
}
// NewUpdateShareBadRequest creates UpdateShareBadRequest with default headers values
func NewUpdateShareBadRequest() *UpdateShareBadRequest {
return &UpdateShareBadRequest{}
}
// WriteResponse to the client
func (o *UpdateShareBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses
rw.WriteHeader(400)
}
// UpdateShareUnauthorizedCode is the HTTP code returned for type UpdateShareUnauthorized
const UpdateShareUnauthorizedCode int = 401

View File

@ -20,6 +20,13 @@ const (
PublicShareMode ShareMode = "public"
)
type PermissionMode string
const (
OpenPermissionMode PermissionMode = "open"
ClosedPermissionMode PermissionMode = "closed"
)
type ShareRequest struct {
Reserved bool
UniqueName string
@ -31,6 +38,8 @@ type ShareRequest struct {
OauthProvider string
OauthEmailAddressPatterns []string
OauthAuthorizationCheckInterval time.Duration
PermissionMode PermissionMode
AccessGrants []string
}
type Share struct {

View File

@ -71,6 +71,8 @@ func newPrivateShare(root env_core.Root, request *ShareRequest) *share.SharePara
BackendMode: string(request.BackendMode),
BackendProxyEndpoint: request.Target,
AuthScheme: string(None),
PermissionMode: string(request.PermissionMode),
AccessGrants: request.AccessGrants,
}
return req
}
@ -87,6 +89,8 @@ func newPublicShare(root env_core.Root, request *ShareRequest) *share.ShareParam
OauthEmailDomains: request.OauthEmailAddressPatterns,
OauthProvider: request.OauthProvider,
OauthAuthorizationCheckInterval: request.OauthAuthorizationCheckInterval.String(),
PermissionMode: string(request.PermissionMode),
AccessGrants: request.AccessGrants,
}
return req
}

View File

@ -39,6 +39,8 @@ class ShareRequest(object):
'oauth_email_domains': 'list[str]',
'oauth_authorization_check_interval': 'str',
'reserved': 'bool',
'permission_mode': 'str',
'access_grants': 'list[str]',
'unique_name': 'str'
}
@ -54,10 +56,12 @@ class ShareRequest(object):
'oauth_email_domains': 'oauthEmailDomains',
'oauth_authorization_check_interval': 'oauthAuthorizationCheckInterval',
'reserved': 'reserved',
'permission_mode': 'permissionMode',
'access_grants': 'accessGrants',
'unique_name': 'uniqueName'
}
def __init__(self, env_zid=None, share_mode=None, frontend_selection=None, backend_mode=None, backend_proxy_endpoint=None, auth_scheme=None, auth_users=None, oauth_provider=None, oauth_email_domains=None, oauth_authorization_check_interval=None, reserved=None, unique_name=None): # noqa: E501
def __init__(self, env_zid=None, share_mode=None, frontend_selection=None, backend_mode=None, backend_proxy_endpoint=None, auth_scheme=None, auth_users=None, oauth_provider=None, oauth_email_domains=None, oauth_authorization_check_interval=None, reserved=None, permission_mode=None, access_grants=None, unique_name=None): # noqa: E501
"""ShareRequest - a model defined in Swagger""" # noqa: E501
self._env_zid = None
self._share_mode = None
@ -70,6 +74,8 @@ class ShareRequest(object):
self._oauth_email_domains = None
self._oauth_authorization_check_interval = None
self._reserved = None
self._permission_mode = None
self._access_grants = None
self._unique_name = None
self.discriminator = None
if env_zid is not None:
@ -94,6 +100,10 @@ class ShareRequest(object):
self.oauth_authorization_check_interval = oauth_authorization_check_interval
if reserved is not None:
self.reserved = reserved
if permission_mode is not None:
self.permission_mode = permission_mode
if access_grants is not None:
self.access_grants = access_grants
if unique_name is not None:
self.unique_name = unique_name
@ -346,6 +356,54 @@ class ShareRequest(object):
self._reserved = reserved
@property
def permission_mode(self):
"""Gets the permission_mode of this ShareRequest. # noqa: E501
:return: The permission_mode of this ShareRequest. # noqa: E501
:rtype: str
"""
return self._permission_mode
@permission_mode.setter
def permission_mode(self, permission_mode):
"""Sets the permission_mode of this ShareRequest.
:param permission_mode: The permission_mode of this ShareRequest. # noqa: E501
:type: str
"""
allowed_values = ["open", "closed"] # noqa: E501
if permission_mode not in allowed_values:
raise ValueError(
"Invalid value for `permission_mode` ({0}), must be one of {1}" # noqa: E501
.format(permission_mode, allowed_values)
)
self._permission_mode = permission_mode
@property
def access_grants(self):
"""Gets the access_grants of this ShareRequest. # noqa: E501
:return: The access_grants of this ShareRequest. # noqa: E501
:rtype: list[str]
"""
return self._access_grants
@access_grants.setter
def access_grants(self, access_grants):
"""Sets the access_grants of this ShareRequest.
:param access_grants: The access_grants of this ShareRequest. # noqa: E501
:type: list[str]
"""
self._access_grants = access_grants
@property
def unique_name(self):
"""Gets the unique_name of this ShareRequest. # noqa: E501

View File

@ -29,23 +29,33 @@ class UpdateShareRequest(object):
"""
swagger_types = {
'shr_token': 'str',
'backend_proxy_endpoint': 'str'
'backend_proxy_endpoint': 'str',
'add_access_grants': 'list[str]',
'remove_access_grants': 'list[str]'
}
attribute_map = {
'shr_token': 'shrToken',
'backend_proxy_endpoint': 'backendProxyEndpoint'
'backend_proxy_endpoint': 'backendProxyEndpoint',
'add_access_grants': 'addAccessGrants',
'remove_access_grants': 'removeAccessGrants'
}
def __init__(self, shr_token=None, backend_proxy_endpoint=None): # noqa: E501
def __init__(self, shr_token=None, backend_proxy_endpoint=None, add_access_grants=None, remove_access_grants=None): # noqa: E501
"""UpdateShareRequest - a model defined in Swagger""" # noqa: E501
self._shr_token = None
self._backend_proxy_endpoint = None
self._add_access_grants = None
self._remove_access_grants = None
self.discriminator = None
if shr_token is not None:
self.shr_token = shr_token
if backend_proxy_endpoint is not None:
self.backend_proxy_endpoint = backend_proxy_endpoint
if add_access_grants is not None:
self.add_access_grants = add_access_grants
if remove_access_grants is not None:
self.remove_access_grants = remove_access_grants
@property
def shr_token(self):
@ -89,6 +99,48 @@ class UpdateShareRequest(object):
self._backend_proxy_endpoint = backend_proxy_endpoint
@property
def add_access_grants(self):
"""Gets the add_access_grants of this UpdateShareRequest. # noqa: E501
:return: The add_access_grants of this UpdateShareRequest. # noqa: E501
:rtype: list[str]
"""
return self._add_access_grants
@add_access_grants.setter
def add_access_grants(self, add_access_grants):
"""Sets the add_access_grants of this UpdateShareRequest.
:param add_access_grants: The add_access_grants of this UpdateShareRequest. # noqa: E501
:type: list[str]
"""
self._add_access_grants = add_access_grants
@property
def remove_access_grants(self):
"""Gets the remove_access_grants of this UpdateShareRequest. # noqa: E501
:return: The remove_access_grants of this UpdateShareRequest. # noqa: E501
:rtype: list[str]
"""
return self._remove_access_grants
@remove_access_grants.setter
def remove_access_grants(self, remove_access_grants):
"""Sets the remove_access_grants of this UpdateShareRequest.
:param remove_access_grants: The remove_access_grants of this UpdateShareRequest. # noqa: E501
:type: list[str]
"""
self._remove_access_grants = remove_access_grants
def to_dict(self):
"""Returns the model properties as a dict"""
result = {}

View File

@ -651,6 +651,8 @@ paths:
responses:
200:
description: share updated
400:
description: bad request
401:
description: unauthorized
404:
@ -1057,6 +1059,13 @@ definitions:
type: string
reserved:
type: boolean
permissionMode:
type: string
enum: ["open", "closed"]
accessGrants:
type: array
items:
type: string
uniqueName:
type: string
@ -1120,6 +1129,14 @@ definitions:
type: string
backendProxyEndpoint:
type: string
addAccessGrants:
type: array
items:
type: string
removeAccessGrants:
type: array
items:
type: string
verifyRequest:
type: object

View File

@ -267,6 +267,8 @@
* @property {string[]} oauthEmailDomains
* @property {string} oauthAuthorizationCheckInterval
* @property {boolean} reserved
* @property {string} permissionMode
* @property {string[]} accessGrants
* @property {string} uniqueName
*/
@ -319,6 +321,8 @@
*
* @property {string} shrToken
* @property {string} backendProxyEndpoint
* @property {string[]} addAccessGrants
* @property {string[]} removeAccessGrants
*/
/**