From a26cf19ade591e61f0368abcd2ba6846323faaea Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Wed, 4 Sep 2024 16:10:19 -0400 Subject: [PATCH 1/8] new '/grants' endpoint for synchronizing grants for an account (#744) --- rest_client_zrok/admin/admin_client.go | 41 +++ rest_client_zrok/admin/grants_parameters.go | 146 ++++++++++ rest_client_zrok/admin/grants_responses.go | 252 ++++++++++++++++++ rest_server_zrok/embedded_spec.go | 74 +++++ rest_server_zrok/operations/admin/grants.go | 111 ++++++++ .../operations/admin/grants_parameters.go | 74 +++++ .../operations/admin/grants_responses.go | 87 ++++++ .../operations/admin/grants_urlbuilder.go | 87 ++++++ rest_server_zrok/operations/zrok_api.go | 12 + .../sdk/src/zrok/api/.openapi-generator/FILES | 1 + sdk/nodejs/sdk/src/zrok/api/api/adminApi.ts | 59 ++++ .../sdk/src/zrok/api/model/grantsRequest.ts | 31 +++ sdk/nodejs/sdk/src/zrok/api/model/models.ts | 3 + sdk/python/sdk/zrok/zrok_api/__init__.py | 1 + sdk/python/sdk/zrok/zrok_api/api/admin_api.py | 89 +++++++ .../sdk/zrok/zrok_api/models/__init__.py | 1 + .../sdk/zrok/zrok_api/models/grants_body.py | 110 ++++++++ specs/zrok.yml | 23 ++ ui/src/api/admin.js | 26 ++ 19 files changed, 1228 insertions(+) create mode 100644 rest_client_zrok/admin/grants_parameters.go create mode 100644 rest_client_zrok/admin/grants_responses.go create mode 100644 rest_server_zrok/operations/admin/grants.go create mode 100644 rest_server_zrok/operations/admin/grants_parameters.go create mode 100644 rest_server_zrok/operations/admin/grants_responses.go create mode 100644 rest_server_zrok/operations/admin/grants_urlbuilder.go create mode 100644 sdk/nodejs/sdk/src/zrok/api/model/grantsRequest.ts create mode 100644 sdk/python/sdk/zrok/zrok_api/models/grants_body.py diff --git a/rest_client_zrok/admin/admin_client.go b/rest_client_zrok/admin/admin_client.go index 5d8e8906a..2f37aa4e7 100644 --- a/rest_client_zrok/admin/admin_client.go +++ b/rest_client_zrok/admin/admin_client.go @@ -38,6 +38,8 @@ type ClientService interface { DeleteFrontend(params *DeleteFrontendParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*DeleteFrontendOK, error) + Grants(params *GrantsParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*GrantsOK, error) + InviteTokenGenerate(params *InviteTokenGenerateParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*InviteTokenGenerateCreated, error) ListFrontends(params *ListFrontendsParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*ListFrontendsOK, error) @@ -203,6 +205,45 @@ func (a *Client) DeleteFrontend(params *DeleteFrontendParams, authInfo runtime.C panic(msg) } +/* +Grants grants API +*/ +func (a *Client) Grants(params *GrantsParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*GrantsOK, error) { + // TODO: Validate the params before sending + if params == nil { + params = NewGrantsParams() + } + op := &runtime.ClientOperation{ + ID: "grants", + Method: "POST", + PathPattern: "/grants", + ProducesMediaTypes: []string{"application/zrok.v1+json"}, + ConsumesMediaTypes: []string{"application/zrok.v1+json"}, + Schemes: []string{"http"}, + Params: params, + Reader: &GrantsReader{formats: a.formats}, + AuthInfo: authInfo, + Context: params.Context, + Client: params.HTTPClient, + } + for _, opt := range opts { + opt(op) + } + + result, err := a.transport.Submit(op) + if err != nil { + return nil, err + } + success, ok := result.(*GrantsOK) + if ok { + return success, nil + } + // unexpected success response + // safeguard: normally, absent a default response, unknown success responses return an error above: so this is a codegen issue + msg := fmt.Sprintf("unexpected success response for grants: API contract not enforced by server. Client expected to get an error, but got: %T", result) + panic(msg) +} + /* InviteTokenGenerate invite token generate API */ diff --git a/rest_client_zrok/admin/grants_parameters.go b/rest_client_zrok/admin/grants_parameters.go new file mode 100644 index 000000000..8e114c7df --- /dev/null +++ b/rest_client_zrok/admin/grants_parameters.go @@ -0,0 +1,146 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package admin + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" +) + +// NewGrantsParams creates a new GrantsParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewGrantsParams() *GrantsParams { + return &GrantsParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewGrantsParamsWithTimeout creates a new GrantsParams object +// with the ability to set a timeout on a request. +func NewGrantsParamsWithTimeout(timeout time.Duration) *GrantsParams { + return &GrantsParams{ + timeout: timeout, + } +} + +// NewGrantsParamsWithContext creates a new GrantsParams object +// with the ability to set a context for a request. +func NewGrantsParamsWithContext(ctx context.Context) *GrantsParams { + return &GrantsParams{ + Context: ctx, + } +} + +// NewGrantsParamsWithHTTPClient creates a new GrantsParams object +// with the ability to set a custom HTTPClient for a request. +func NewGrantsParamsWithHTTPClient(client *http.Client) *GrantsParams { + return &GrantsParams{ + HTTPClient: client, + } +} + +/* +GrantsParams contains all the parameters to send to the API endpoint + + for the grants operation. + + Typically these are written to a http.Request. +*/ +type GrantsParams struct { + + // Body. + Body GrantsBody + + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the grants params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *GrantsParams) WithDefaults() *GrantsParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the grants params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *GrantsParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the grants params +func (o *GrantsParams) WithTimeout(timeout time.Duration) *GrantsParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the grants params +func (o *GrantsParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the grants params +func (o *GrantsParams) WithContext(ctx context.Context) *GrantsParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the grants params +func (o *GrantsParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the grants params +func (o *GrantsParams) WithHTTPClient(client *http.Client) *GrantsParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the grants params +func (o *GrantsParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WithBody adds the body to the grants params +func (o *GrantsParams) WithBody(body GrantsBody) *GrantsParams { + o.SetBody(body) + return o +} + +// SetBody adds the body to the grants params +func (o *GrantsParams) SetBody(body GrantsBody) { + o.Body = body +} + +// WriteToRequest writes these params to a swagger request +func (o *GrantsParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + if err := r.SetBodyParam(o.Body); err != nil { + return err + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/rest_client_zrok/admin/grants_responses.go b/rest_client_zrok/admin/grants_responses.go new file mode 100644 index 000000000..79354d887 --- /dev/null +++ b/rest_client_zrok/admin/grants_responses.go @@ -0,0 +1,252 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package admin + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "fmt" + + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// GrantsReader is a Reader for the Grants structure. +type GrantsReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *GrantsReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) { + switch response.Code() { + case 200: + result := NewGrantsOK() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + case 401: + result := NewGrantsUnauthorized() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return nil, result + case 500: + result := NewGrantsInternalServerError() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return nil, result + default: + return nil, runtime.NewAPIError("[POST /grants] grants", response, response.Code()) + } +} + +// NewGrantsOK creates a GrantsOK with default headers values +func NewGrantsOK() *GrantsOK { + return &GrantsOK{} +} + +/* +GrantsOK describes a response with status code 200, with default header values. + +ok +*/ +type GrantsOK struct { +} + +// IsSuccess returns true when this grants o k response has a 2xx status code +func (o *GrantsOK) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this grants o k response has a 3xx status code +func (o *GrantsOK) IsRedirect() bool { + return false +} + +// IsClientError returns true when this grants o k response has a 4xx status code +func (o *GrantsOK) IsClientError() bool { + return false +} + +// IsServerError returns true when this grants o k response has a 5xx status code +func (o *GrantsOK) IsServerError() bool { + return false +} + +// IsCode returns true when this grants o k response a status code equal to that given +func (o *GrantsOK) IsCode(code int) bool { + return code == 200 +} + +// Code gets the status code for the grants o k response +func (o *GrantsOK) Code() int { + return 200 +} + +func (o *GrantsOK) Error() string { + return fmt.Sprintf("[POST /grants][%d] grantsOK ", 200) +} + +func (o *GrantsOK) String() string { + return fmt.Sprintf("[POST /grants][%d] grantsOK ", 200) +} + +func (o *GrantsOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + return nil +} + +// NewGrantsUnauthorized creates a GrantsUnauthorized with default headers values +func NewGrantsUnauthorized() *GrantsUnauthorized { + return &GrantsUnauthorized{} +} + +/* +GrantsUnauthorized describes a response with status code 401, with default header values. + +unauthorized +*/ +type GrantsUnauthorized struct { +} + +// IsSuccess returns true when this grants unauthorized response has a 2xx status code +func (o *GrantsUnauthorized) IsSuccess() bool { + return false +} + +// IsRedirect returns true when this grants unauthorized response has a 3xx status code +func (o *GrantsUnauthorized) IsRedirect() bool { + return false +} + +// IsClientError returns true when this grants unauthorized response has a 4xx status code +func (o *GrantsUnauthorized) IsClientError() bool { + return true +} + +// IsServerError returns true when this grants unauthorized response has a 5xx status code +func (o *GrantsUnauthorized) IsServerError() bool { + return false +} + +// IsCode returns true when this grants unauthorized response a status code equal to that given +func (o *GrantsUnauthorized) IsCode(code int) bool { + return code == 401 +} + +// Code gets the status code for the grants unauthorized response +func (o *GrantsUnauthorized) Code() int { + return 401 +} + +func (o *GrantsUnauthorized) Error() string { + return fmt.Sprintf("[POST /grants][%d] grantsUnauthorized ", 401) +} + +func (o *GrantsUnauthorized) String() string { + return fmt.Sprintf("[POST /grants][%d] grantsUnauthorized ", 401) +} + +func (o *GrantsUnauthorized) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + return nil +} + +// NewGrantsInternalServerError creates a GrantsInternalServerError with default headers values +func NewGrantsInternalServerError() *GrantsInternalServerError { + return &GrantsInternalServerError{} +} + +/* +GrantsInternalServerError describes a response with status code 500, with default header values. + +internal server error +*/ +type GrantsInternalServerError struct { +} + +// IsSuccess returns true when this grants internal server error response has a 2xx status code +func (o *GrantsInternalServerError) IsSuccess() bool { + return false +} + +// IsRedirect returns true when this grants internal server error response has a 3xx status code +func (o *GrantsInternalServerError) IsRedirect() bool { + return false +} + +// IsClientError returns true when this grants internal server error response has a 4xx status code +func (o *GrantsInternalServerError) IsClientError() bool { + return false +} + +// IsServerError returns true when this grants internal server error response has a 5xx status code +func (o *GrantsInternalServerError) IsServerError() bool { + return true +} + +// IsCode returns true when this grants internal server error response a status code equal to that given +func (o *GrantsInternalServerError) IsCode(code int) bool { + return code == 500 +} + +// Code gets the status code for the grants internal server error response +func (o *GrantsInternalServerError) Code() int { + return 500 +} + +func (o *GrantsInternalServerError) Error() string { + return fmt.Sprintf("[POST /grants][%d] grantsInternalServerError ", 500) +} + +func (o *GrantsInternalServerError) String() string { + return fmt.Sprintf("[POST /grants][%d] grantsInternalServerError ", 500) +} + +func (o *GrantsInternalServerError) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + return nil +} + +/* +GrantsBody grants body +swagger:model GrantsBody +*/ +type GrantsBody struct { + + // email + Email string `json:"email,omitempty"` +} + +// Validate validates this grants body +func (o *GrantsBody) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this grants body based on context it is used +func (o *GrantsBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (o *GrantsBody) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *GrantsBody) UnmarshalBinary(b []byte) error { + var res GrantsBody + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} diff --git a/rest_server_zrok/embedded_spec.go b/rest_server_zrok/embedded_spec.go index 50094c645..ed565e454 100644 --- a/rest_server_zrok/embedded_spec.go +++ b/rest_server_zrok/embedded_spec.go @@ -526,6 +526,43 @@ func init() { } } }, + "/grants": { + "post": { + "security": [ + { + "key": [] + } + ], + "tags": [ + "admin" + ], + "operationId": "grants", + "parameters": [ + { + "name": "body", + "in": "body", + "schema": { + "properties": { + "email": { + "type": "string" + } + } + } + } + ], + "responses": { + "200": { + "description": "ok" + }, + "401": { + "description": "unauthorized" + }, + "500": { + "description": "internal server error" + } + } + } + }, "/identity": { "post": { "security": [ @@ -2336,6 +2373,43 @@ func init() { } } }, + "/grants": { + "post": { + "security": [ + { + "key": [] + } + ], + "tags": [ + "admin" + ], + "operationId": "grants", + "parameters": [ + { + "name": "body", + "in": "body", + "schema": { + "properties": { + "email": { + "type": "string" + } + } + } + } + ], + "responses": { + "200": { + "description": "ok" + }, + "401": { + "description": "unauthorized" + }, + "500": { + "description": "internal server error" + } + } + } + }, "/identity": { "post": { "security": [ diff --git a/rest_server_zrok/operations/admin/grants.go b/rest_server_zrok/operations/admin/grants.go new file mode 100644 index 000000000..b23fbe4ef --- /dev/null +++ b/rest_server_zrok/operations/admin/grants.go @@ -0,0 +1,111 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package admin + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "context" + "net/http" + + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + + "github.com/openziti/zrok/rest_model_zrok" +) + +// GrantsHandlerFunc turns a function with the right signature into a grants handler +type GrantsHandlerFunc func(GrantsParams, *rest_model_zrok.Principal) middleware.Responder + +// Handle executing the request and returning a response +func (fn GrantsHandlerFunc) Handle(params GrantsParams, principal *rest_model_zrok.Principal) middleware.Responder { + return fn(params, principal) +} + +// GrantsHandler interface for that can handle valid grants params +type GrantsHandler interface { + Handle(GrantsParams, *rest_model_zrok.Principal) middleware.Responder +} + +// NewGrants creates a new http.Handler for the grants operation +func NewGrants(ctx *middleware.Context, handler GrantsHandler) *Grants { + return &Grants{Context: ctx, Handler: handler} +} + +/* + Grants swagger:route POST /grants admin grants + +Grants grants API +*/ +type Grants struct { + Context *middleware.Context + Handler GrantsHandler +} + +func (o *Grants) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewGrantsParams() + uprinc, aCtx, err := o.Context.Authorize(r, route) + if err != nil { + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + if aCtx != nil { + *r = *aCtx + } + var principal *rest_model_zrok.Principal + if uprinc != nil { + principal = uprinc.(*rest_model_zrok.Principal) // this is really a rest_model_zrok.Principal, I promise + } + + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params, principal) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} + +// GrantsBody grants body +// +// swagger:model GrantsBody +type GrantsBody struct { + + // email + Email string `json:"email,omitempty"` +} + +// Validate validates this grants body +func (o *GrantsBody) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this grants body based on context it is used +func (o *GrantsBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (o *GrantsBody) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *GrantsBody) UnmarshalBinary(b []byte) error { + var res GrantsBody + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} diff --git a/rest_server_zrok/operations/admin/grants_parameters.go b/rest_server_zrok/operations/admin/grants_parameters.go new file mode 100644 index 000000000..62fa6f1b4 --- /dev/null +++ b/rest_server_zrok/operations/admin/grants_parameters.go @@ -0,0 +1,74 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package admin + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/validate" +) + +// NewGrantsParams creates a new GrantsParams object +// +// There are no default values defined in the spec. +func NewGrantsParams() GrantsParams { + + return GrantsParams{} +} + +// GrantsParams contains all the bound params for the grants operation +// typically these are obtained from a http.Request +// +// swagger:parameters grants +type GrantsParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + In: body + */ + Body GrantsBody +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewGrantsParams() beforehand. +func (o *GrantsParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + if runtime.HasBody(r) { + defer r.Body.Close() + var body GrantsBody + if err := route.Consumer.Consume(r.Body, &body); err != nil { + res = append(res, errors.NewParseError("body", "body", "", err)) + } else { + // validate body object + if err := body.Validate(route.Formats); err != nil { + res = append(res, err) + } + + ctx := validate.WithOperationRequest(r.Context()) + if err := body.ContextValidate(ctx, route.Formats); err != nil { + res = append(res, err) + } + + if len(res) == 0 { + o.Body = body + } + } + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/rest_server_zrok/operations/admin/grants_responses.go b/rest_server_zrok/operations/admin/grants_responses.go new file mode 100644 index 000000000..37fd7af1f --- /dev/null +++ b/rest_server_zrok/operations/admin/grants_responses.go @@ -0,0 +1,87 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package admin + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" +) + +// GrantsOKCode is the HTTP code returned for type GrantsOK +const GrantsOKCode int = 200 + +/* +GrantsOK ok + +swagger:response grantsOK +*/ +type GrantsOK struct { +} + +// NewGrantsOK creates GrantsOK with default headers values +func NewGrantsOK() *GrantsOK { + + return &GrantsOK{} +} + +// WriteResponse to the client +func (o *GrantsOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + + rw.WriteHeader(200) +} + +// GrantsUnauthorizedCode is the HTTP code returned for type GrantsUnauthorized +const GrantsUnauthorizedCode int = 401 + +/* +GrantsUnauthorized unauthorized + +swagger:response grantsUnauthorized +*/ +type GrantsUnauthorized struct { +} + +// NewGrantsUnauthorized creates GrantsUnauthorized with default headers values +func NewGrantsUnauthorized() *GrantsUnauthorized { + + return &GrantsUnauthorized{} +} + +// WriteResponse to the client +func (o *GrantsUnauthorized) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + + rw.WriteHeader(401) +} + +// GrantsInternalServerErrorCode is the HTTP code returned for type GrantsInternalServerError +const GrantsInternalServerErrorCode int = 500 + +/* +GrantsInternalServerError internal server error + +swagger:response grantsInternalServerError +*/ +type GrantsInternalServerError struct { +} + +// NewGrantsInternalServerError creates GrantsInternalServerError with default headers values +func NewGrantsInternalServerError() *GrantsInternalServerError { + + return &GrantsInternalServerError{} +} + +// WriteResponse to the client +func (o *GrantsInternalServerError) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + + rw.WriteHeader(500) +} diff --git a/rest_server_zrok/operations/admin/grants_urlbuilder.go b/rest_server_zrok/operations/admin/grants_urlbuilder.go new file mode 100644 index 000000000..6d023a9ef --- /dev/null +++ b/rest_server_zrok/operations/admin/grants_urlbuilder.go @@ -0,0 +1,87 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package admin + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" +) + +// GrantsURL generates an URL for the grants operation +type GrantsURL struct { + _basePath string +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GrantsURL) WithBasePath(bp string) *GrantsURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GrantsURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *GrantsURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/grants" + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *GrantsURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *GrantsURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *GrantsURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on GrantsURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on GrantsURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *GrantsURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/rest_server_zrok/operations/zrok_api.go b/rest_server_zrok/operations/zrok_api.go index c8bbebb0d..9c86bfd6c 100644 --- a/rest_server_zrok/operations/zrok_api.go +++ b/rest_server_zrok/operations/zrok_api.go @@ -97,6 +97,9 @@ func NewZrokAPI(spec *loads.Document) *ZrokAPI { MetadataGetShareMetricsHandler: metadata.GetShareMetricsHandlerFunc(func(params metadata.GetShareMetricsParams, principal *rest_model_zrok.Principal) middleware.Responder { return middleware.NotImplemented("operation metadata.GetShareMetrics has not yet been implemented") }), + AdminGrantsHandler: admin.GrantsHandlerFunc(func(params admin.GrantsParams, principal *rest_model_zrok.Principal) middleware.Responder { + return middleware.NotImplemented("operation admin.Grants has not yet been implemented") + }), AccountInviteHandler: account.InviteHandlerFunc(func(params account.InviteParams) middleware.Responder { return middleware.NotImplemented("operation account.Invite has not yet been implemented") }), @@ -227,6 +230,8 @@ type ZrokAPI struct { MetadataGetShareDetailHandler metadata.GetShareDetailHandler // MetadataGetShareMetricsHandler sets the operation handler for the get share metrics operation MetadataGetShareMetricsHandler metadata.GetShareMetricsHandler + // AdminGrantsHandler sets the operation handler for the grants operation + AdminGrantsHandler admin.GrantsHandler // AccountInviteHandler sets the operation handler for the invite operation AccountInviteHandler account.InviteHandler // AdminInviteTokenGenerateHandler sets the operation handler for the invite token generate operation @@ -388,6 +393,9 @@ func (o *ZrokAPI) Validate() error { if o.MetadataGetShareMetricsHandler == nil { unregistered = append(unregistered, "metadata.GetShareMetricsHandler") } + if o.AdminGrantsHandler == nil { + unregistered = append(unregistered, "admin.GrantsHandler") + } if o.AccountInviteHandler == nil { unregistered = append(unregistered, "account.InviteHandler") } @@ -602,6 +610,10 @@ func (o *ZrokAPI) initHandlerCache() { if o.handlers["POST"] == nil { o.handlers["POST"] = make(map[string]http.Handler) } + o.handlers["POST"]["/grants"] = admin.NewGrants(o.context, o.AdminGrantsHandler) + if o.handlers["POST"] == nil { + o.handlers["POST"] = make(map[string]http.Handler) + } o.handlers["POST"]["/invite"] = account.NewInvite(o.context, o.AccountInviteHandler) if o.handlers["POST"] == nil { o.handlers["POST"] = make(map[string]http.Handler) diff --git a/sdk/nodejs/sdk/src/zrok/api/.openapi-generator/FILES b/sdk/nodejs/sdk/src/zrok/api/.openapi-generator/FILES index ff51caaf5..db21b7d1b 100644 --- a/sdk/nodejs/sdk/src/zrok/api/.openapi-generator/FILES +++ b/sdk/nodejs/sdk/src/zrok/api/.openapi-generator/FILES @@ -23,6 +23,7 @@ model/enableResponse.ts model/environment.ts model/environmentAndResources.ts model/frontend.ts +model/grantsRequest.ts model/inviteRequest.ts model/inviteTokenGenerateRequest.ts model/loginRequest.ts diff --git a/sdk/nodejs/sdk/src/zrok/api/api/adminApi.ts b/sdk/nodejs/sdk/src/zrok/api/api/adminApi.ts index 0dd53832d..591bfddec 100644 --- a/sdk/nodejs/sdk/src/zrok/api/api/adminApi.ts +++ b/sdk/nodejs/sdk/src/zrok/api/api/adminApi.ts @@ -21,6 +21,7 @@ import { CreateFrontendResponse } from '../model/createFrontendResponse'; import { CreateIdentity201Response } from '../model/createIdentity201Response'; import { CreateIdentityRequest } from '../model/createIdentityRequest'; import { DeleteFrontendRequest } from '../model/deleteFrontendRequest'; +import { GrantsRequest } from '../model/grantsRequest'; import { InviteTokenGenerateRequest } from '../model/inviteTokenGenerateRequest'; import { PublicFrontend } from '../model/publicFrontend'; import { RegenerateToken200Response } from '../model/regenerateToken200Response'; @@ -354,6 +355,64 @@ export class AdminApi { }); }); } + /** + * + * @param body + */ + public async grants (body?: GrantsRequest, options: {headers: {[name: string]: string}} = {headers: {}}) : Promise<{ response: http.IncomingMessage; body?: any; }> { + const localVarPath = this.basePath + '/grants'; + let localVarQueryParameters: any = {}; + let localVarHeaderParams: any = (Object).assign({}, this._defaultHeaders); + let localVarFormParams: any = {}; + + (Object).assign(localVarHeaderParams, options.headers); + + let localVarUseFormData = false; + + let localVarRequestOptions: localVarRequest.Options = { + method: 'POST', + qs: localVarQueryParameters, + headers: localVarHeaderParams, + uri: localVarPath, + useQuerystring: this._useQuerystring, + json: true, + body: ObjectSerializer.serialize(body, "GrantsRequest") + }; + + let authenticationPromise = Promise.resolve(); + if (this.authentications.key.apiKey) { + authenticationPromise = authenticationPromise.then(() => this.authentications.key.applyToRequest(localVarRequestOptions)); + } + authenticationPromise = authenticationPromise.then(() => this.authentications.default.applyToRequest(localVarRequestOptions)); + + let interceptorPromise = authenticationPromise; + for (const interceptor of this.interceptors) { + interceptorPromise = interceptorPromise.then(() => interceptor(localVarRequestOptions)); + } + + return interceptorPromise.then(() => { + if (Object.keys(localVarFormParams).length) { + if (localVarUseFormData) { + (localVarRequestOptions).formData = localVarFormParams; + } else { + localVarRequestOptions.form = localVarFormParams; + } + } + return new Promise<{ response: http.IncomingMessage; body?: any; }>((resolve, reject) => { + localVarRequest(localVarRequestOptions, (error, response, body) => { + if (error) { + reject(error); + } else { + if (response.statusCode && response.statusCode >= 200 && response.statusCode <= 299) { + resolve({ response: response, body: body }); + } else { + reject(new HttpError(response, body, response.statusCode)); + } + } + }); + }); + }); + } /** * * @param body diff --git a/sdk/nodejs/sdk/src/zrok/api/model/grantsRequest.ts b/sdk/nodejs/sdk/src/zrok/api/model/grantsRequest.ts new file mode 100644 index 000000000..1018c48f2 --- /dev/null +++ b/sdk/nodejs/sdk/src/zrok/api/model/grantsRequest.ts @@ -0,0 +1,31 @@ +/** + * zrok + * zrok client access + * + * The version of the OpenAPI document: 0.3.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { RequestFile } from './models'; + +export class GrantsRequest { + 'email'?: string; + + static discriminator: string | undefined = undefined; + + static attributeTypeMap: Array<{name: string, baseName: string, type: string}> = [ + { + "name": "email", + "baseName": "email", + "type": "string" + } ]; + + static getAttributeTypeMap() { + return GrantsRequest.attributeTypeMap; + } +} + diff --git a/sdk/nodejs/sdk/src/zrok/api/model/models.ts b/sdk/nodejs/sdk/src/zrok/api/model/models.ts index 8d4676924..f2a3e3ca7 100644 --- a/sdk/nodejs/sdk/src/zrok/api/model/models.ts +++ b/sdk/nodejs/sdk/src/zrok/api/model/models.ts @@ -17,6 +17,7 @@ export * from './enableResponse'; export * from './environment'; export * from './environmentAndResources'; export * from './frontend'; +export * from './grantsRequest'; export * from './inviteRequest'; export * from './inviteTokenGenerateRequest'; export * from './loginRequest'; @@ -72,6 +73,7 @@ import { EnableResponse } from './enableResponse'; import { Environment } from './environment'; import { EnvironmentAndResources } from './environmentAndResources'; import { Frontend } from './frontend'; +import { GrantsRequest } from './grantsRequest'; import { InviteRequest } from './inviteRequest'; import { InviteTokenGenerateRequest } from './inviteTokenGenerateRequest'; import { LoginRequest } from './loginRequest'; @@ -135,6 +137,7 @@ let typeMap: {[index: string]: any} = { "Environment": Environment, "EnvironmentAndResources": EnvironmentAndResources, "Frontend": Frontend, + "GrantsRequest": GrantsRequest, "InviteRequest": InviteRequest, "InviteTokenGenerateRequest": InviteTokenGenerateRequest, "LoginRequest": LoginRequest, diff --git a/sdk/python/sdk/zrok/zrok_api/__init__.py b/sdk/python/sdk/zrok/zrok_api/__init__.py index 931e9b6ec..dcf61c55a 100644 --- a/sdk/python/sdk/zrok/zrok_api/__init__.py +++ b/sdk/python/sdk/zrok/zrok_api/__init__.py @@ -42,6 +42,7 @@ from zrok_api.models.error_message import ErrorMessage from zrok_api.models.frontend import Frontend from zrok_api.models.frontends import Frontends +from zrok_api.models.grants_body import GrantsBody from zrok_api.models.identity_body import IdentityBody from zrok_api.models.inline_response200 import InlineResponse200 from zrok_api.models.inline_response201 import InlineResponse201 diff --git a/sdk/python/sdk/zrok/zrok_api/api/admin_api.py b/sdk/python/sdk/zrok/zrok_api/api/admin_api.py index 8cc4f06f3..8ea807e34 100644 --- a/sdk/python/sdk/zrok/zrok_api/api/admin_api.py +++ b/sdk/python/sdk/zrok/zrok_api/api/admin_api.py @@ -400,6 +400,95 @@ def delete_frontend_with_http_info(self, **kwargs): # noqa: E501 _request_timeout=params.get('_request_timeout'), collection_formats=collection_formats) + def grants(self, **kwargs): # noqa: E501 + """grants # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.grants(async_req=True) + >>> result = thread.get() + + :param async_req bool + :param GrantsBody body: + :return: None + If the method is called asynchronously, + returns the request thread. + """ + kwargs['_return_http_data_only'] = True + if kwargs.get('async_req'): + return self.grants_with_http_info(**kwargs) # noqa: E501 + else: + (data) = self.grants_with_http_info(**kwargs) # noqa: E501 + return data + + def grants_with_http_info(self, **kwargs): # noqa: E501 + """grants # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.grants_with_http_info(async_req=True) + >>> result = thread.get() + + :param async_req bool + :param GrantsBody body: + :return: None + If the method is called asynchronously, + returns the request thread. + """ + + all_params = ['body'] # noqa: E501 + all_params.append('async_req') + all_params.append('_return_http_data_only') + all_params.append('_preload_content') + all_params.append('_request_timeout') + + params = locals() + for key, val in six.iteritems(params['kwargs']): + if key not in all_params: + raise TypeError( + "Got an unexpected keyword argument '%s'" + " to method grants" % key + ) + params[key] = val + del params['kwargs'] + + collection_formats = {} + + path_params = {} + + query_params = [] + + header_params = {} + + form_params = [] + local_var_files = {} + + body_params = None + if 'body' in params: + body_params = params['body'] + # HTTP header `Content-Type` + header_params['Content-Type'] = self.api_client.select_header_content_type( # noqa: E501 + ['application/zrok.v1+json']) # noqa: E501 + + # Authentication setting + auth_settings = ['key'] # noqa: E501 + + return self.api_client.call_api( + '/grants', 'POST', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_type=None, # noqa: E501 + auth_settings=auth_settings, + async_req=params.get('async_req'), + _return_http_data_only=params.get('_return_http_data_only'), + _preload_content=params.get('_preload_content', True), + _request_timeout=params.get('_request_timeout'), + collection_formats=collection_formats) + def invite_token_generate(self, **kwargs): # noqa: E501 """invite_token_generate # noqa: E501 diff --git a/sdk/python/sdk/zrok/zrok_api/models/__init__.py b/sdk/python/sdk/zrok/zrok_api/models/__init__.py index 62da832e7..6707a220a 100644 --- a/sdk/python/sdk/zrok/zrok_api/models/__init__.py +++ b/sdk/python/sdk/zrok/zrok_api/models/__init__.py @@ -32,6 +32,7 @@ from zrok_api.models.error_message import ErrorMessage from zrok_api.models.frontend import Frontend from zrok_api.models.frontends import Frontends +from zrok_api.models.grants_body import GrantsBody from zrok_api.models.identity_body import IdentityBody from zrok_api.models.inline_response200 import InlineResponse200 from zrok_api.models.inline_response201 import InlineResponse201 diff --git a/sdk/python/sdk/zrok/zrok_api/models/grants_body.py b/sdk/python/sdk/zrok/zrok_api/models/grants_body.py new file mode 100644 index 000000000..e61ea02d3 --- /dev/null +++ b/sdk/python/sdk/zrok/zrok_api/models/grants_body.py @@ -0,0 +1,110 @@ +# coding: utf-8 + +""" + zrok + + zrok client access # noqa: E501 + + OpenAPI spec version: 0.3.0 + + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class GrantsBody(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'email': 'str' + } + + attribute_map = { + 'email': 'email' + } + + def __init__(self, email=None): # noqa: E501 + """GrantsBody - a model defined in Swagger""" # noqa: E501 + self._email = None + self.discriminator = None + if email is not None: + self.email = email + + @property + def email(self): + """Gets the email of this GrantsBody. # noqa: E501 + + + :return: The email of this GrantsBody. # noqa: E501 + :rtype: str + """ + return self._email + + @email.setter + def email(self, email): + """Sets the email of this GrantsBody. + + + :param email: The email of this GrantsBody. # noqa: E501 + :type: str + """ + + self._email = email + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(GrantsBody, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, GrantsBody): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/specs/zrok.yml b/specs/zrok.yml index a4e1faabc..cc9ef0f87 100644 --- a/specs/zrok.yml +++ b/specs/zrok.yml @@ -307,6 +307,29 @@ paths: 500: description: internal server error + /grants: + post: + tags: + - admin + security: + - key: [] + operationId: grants + parameters: + - name: body + in: body + schema: + properties: + email: + type: string + responses: + 200: + description: ok + 401: + description: unauthorized + 500: + description: internal server error + + /identity: post: tags: diff --git a/ui/src/api/admin.js b/ui/src/api/admin.js index f85fe9364..67bf11ad7 100644 --- a/ui/src/api/admin.js +++ b/ui/src/api/admin.js @@ -68,6 +68,21 @@ export function listFrontends() { return gateway.request(listFrontendsOperation) } +/** + * @param {object} options Optional options + * @param {object} [options.body] + * @return {Promise} ok + */ +export function grants(options) { + if (!options) options = {} + const parameters = { + body: { + body: options.body + } + } + return gateway.request(grantsOperation, parameters) +} + /** * @param {object} options Optional options * @param {object} [options.body] @@ -152,6 +167,17 @@ const listFrontendsOperation = { ] } +const grantsOperation = { + path: '/grants', + contentTypes: ['application/zrok.v1+json'], + method: 'post', + security: [ + { + id: 'key' + } + ] +} + const createIdentityOperation = { path: '/identity', contentTypes: ['application/zrok.v1+json'], From 6db3e5b8777e53fa94bc081091494d49eff41f1b Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Wed, 4 Sep 2024 16:12:07 -0400 Subject: [PATCH 2/8] changelog (#744) --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5d31ad2c..597e109a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## v0.4.40 +FEATURE: New endpoint for synchronizing grants for an account (https://github.com/openziti/zrok/pull/744). Useful for updating the `zrok.proxy.v1` config objects containing interstitial setting when the `skip_interstitial_grants` table has been updated. + FIX: prune incorrect troubleshooting advice about listing Caddy's certificates ## v0.4.39 From 2f5803a541ee554ba92cdd4b83dc5d42d072649f Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Wed, 4 Sep 2024 16:18:20 -0400 Subject: [PATCH 3/8] grants handler wiring (#744) --- controller/controller.go | 1 + controller/grants.go | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 controller/grants.go diff --git a/controller/controller.go b/controller/controller.go index 0ebc134f9..5603ddbd4 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -56,6 +56,7 @@ func Run(inCfg *config.Config) error { api.AdminCreateFrontendHandler = newCreateFrontendHandler() api.AdminCreateIdentityHandler = newCreateIdentityHandler() api.AdminDeleteFrontendHandler = newDeleteFrontendHandler() + api.AdminGrantsHandler = newGrantsHandler() api.AdminInviteTokenGenerateHandler = newInviteTokenGenerateHandler() api.AdminListFrontendsHandler = newListFrontendsHandler() api.AdminUpdateFrontendHandler = newUpdateFrontendHandler() diff --git a/controller/grants.go b/controller/grants.go new file mode 100644 index 000000000..b4f74d564 --- /dev/null +++ b/controller/grants.go @@ -0,0 +1,22 @@ +package controller + +import ( + "github.com/go-openapi/runtime/middleware" + "github.com/openziti/zrok/rest_model_zrok" + "github.com/openziti/zrok/rest_server_zrok/operations/admin" + "github.com/sirupsen/logrus" +) + +type grantsHandler struct{} + +func newGrantsHandler() *grantsHandler { + return &grantsHandler{} +} + +func (h *grantsHandler) Handle(params admin.GrantsParams, principal *rest_model_zrok.Principal) middleware.Responder { + if !principal.Admin { + logrus.Errorf("invalid admin principal") + return admin.NewGrantsUnauthorized() + } + return admin.NewGrantsOK() +} From d2ee09bd44fb039cc8e0f6f8f5e90060c4207f58 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Wed, 4 Sep 2024 17:01:33 -0400 Subject: [PATCH 4/8] lint --- controller/controller.go | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/controller/controller.go b/controller/controller.go index 5603ddbd4..7b210a989 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -2,23 +2,22 @@ package controller import ( "context" + "github.com/go-openapi/loads" + influxdb2 "github.com/influxdata/influxdb-client-go/v2" "github.com/jessevdk/go-flags" "github.com/openziti/zrok/controller/config" "github.com/openziti/zrok/controller/limits" "github.com/openziti/zrok/controller/metrics" - "github.com/sirupsen/logrus" - "log" - "net/http" - _ "net/http/pprof" - - "github.com/go-openapi/loads" - influxdb2 "github.com/influxdata/influxdb-client-go/v2" "github.com/openziti/zrok/controller/store" "github.com/openziti/zrok/rest_server_zrok" "github.com/openziti/zrok/rest_server_zrok/operations" "github.com/openziti/zrok/rest_server_zrok/operations/account" "github.com/openziti/zrok/rest_server_zrok/operations/metadata" "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "log" + "net/http" + _ "net/http/pprof" ) var ( @@ -151,7 +150,3 @@ func Run(inCfg *config.Config) error { return nil } - -func Store() *store.Store { - return str -} From a37009249a90cd98fd71a0ed5571615dad730e15 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Thu, 5 Sep 2024 10:55:21 -0400 Subject: [PATCH 5/8] 'zrok admin grants' CLI (#744) --- cmd/zrok/adminGenerate.go | 2 -- cmd/zrok/adminGrants.go | 51 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 cmd/zrok/adminGrants.go diff --git a/cmd/zrok/adminGenerate.go b/cmd/zrok/adminGenerate.go index 9576646d7..d31c94403 100644 --- a/cmd/zrok/adminGenerate.go +++ b/cmd/zrok/adminGenerate.go @@ -27,9 +27,7 @@ func newAdminGenerateCommand() *adminGenerateCommand { } command := &adminGenerateCommand{cmd: cmd} cmd.Run = command.run - cmd.Flags().IntVarP(&command.amount, "count", "n", 5, "Number of tokens to generate") - return command } diff --git a/cmd/zrok/adminGrants.go b/cmd/zrok/adminGrants.go new file mode 100644 index 000000000..eb9af8995 --- /dev/null +++ b/cmd/zrok/adminGrants.go @@ -0,0 +1,51 @@ +package main + +import ( + "fmt" + "github.com/openziti/zrok/environment" + "github.com/openziti/zrok/rest_client_zrok/admin" + "github.com/spf13/cobra" +) + +func init() { + adminCmd.AddCommand(newAdminGrantsCommand().cmd) +} + +type adminGrantsCommand struct { + cmd *cobra.Command + email string +} + +func newAdminGrantsCommand() *adminGrantsCommand { + cmd := &cobra.Command{ + Use: "grants ", + Short: "Synchronize ziti objects with account grants", + Args: cobra.ExactArgs(1), + } + command := &adminGrantsCommand{cmd: cmd} + cmd.Run = command.run + cmd.Flags().StringVarP(&command.email, "email", "e", "", "email address") + return command +} + +func (command *adminGrantsCommand) run(_ *cobra.Command, args []string) { + env, err := environment.LoadRoot() + if err != nil { + panic(err) + } + + zrok, err := env.Client() + if err != nil { + panic(err) + } + + req := admin.NewGrantsParams() + req.Body.Email = args[0] + + _, err = zrok.Admin.Grants(req, mustGetAdminAuth()) + if err != nil { + panic(err) + } + + fmt.Println("success.") +} From e5aac2358bf9bd66fc29b21bd3d2bbfae9f43969 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Thu, 5 Sep 2024 13:34:07 -0400 Subject: [PATCH 6/8] elaborating the grants handler (#744) --- controller/grants.go | 64 ++++++++++ controller/zrokEdgeSdk/config.go | 36 ++++++ rest_client_zrok/admin/grants_responses.go | 62 ++++++++++ rest_server_zrok/embedded_spec.go | 6 + .../operations/admin/grants_responses.go | 25 ++++ sdk/golang/sdk/config.go | 114 ++++++++++++++++++ specs/zrok.yml | 2 + 7 files changed, 309 insertions(+) diff --git a/controller/grants.go b/controller/grants.go index b4f74d564..7bc3853a5 100644 --- a/controller/grants.go +++ b/controller/grants.go @@ -2,8 +2,10 @@ package controller import ( "github.com/go-openapi/runtime/middleware" + "github.com/openziti/zrok/controller/zrokEdgeSdk" "github.com/openziti/zrok/rest_model_zrok" "github.com/openziti/zrok/rest_server_zrok/operations/admin" + "github.com/openziti/zrok/sdk/golang/sdk" "github.com/sirupsen/logrus" ) @@ -18,5 +20,67 @@ func (h *grantsHandler) Handle(params admin.GrantsParams, principal *rest_model_ logrus.Errorf("invalid admin principal") return admin.NewGrantsUnauthorized() } + + edge, err := zrokEdgeSdk.Client(cfg.Ziti) + if err != nil { + logrus.Errorf("error connecting to ziti: %v", err) + return admin.NewGrantsInternalServerError() + } + + trx, err := str.Begin() + if err != nil { + logrus.Errorf("error starting transaction: %v", err) + return admin.NewGrantsInternalServerError() + } + defer func() { _ = trx.Rollback() }() + + acct, err := str.FindAccountWithEmail(params.Body.Email, trx) + if err != nil { + logrus.Errorf("error finding account with email '%v': %v", params.Body.Email, err) + return admin.NewGrantsNotFound() + } + + acctSkipInterstitial, err := str.IsAccountGrantedSkipInterstitial(acct.Id, trx) + if err != nil { + logrus.Errorf("error checking account '%v' granted skip interstitial: %v", acct.Email, err) + } + + envs, err := str.FindEnvironmentsForAccount(acct.Id, trx) + if err != nil { + logrus.Errorf("error finding environments for '%v': %v", acct.Email, err) + return admin.NewGrantsInternalServerError() + } + + for _, env := range envs { + shrs, err := str.FindSharesForEnvironment(env.Id, trx) + if err != nil { + logrus.Errorf("error finding shares for '%v': %v", acct.Email, err) + return admin.NewGrantsInternalServerError() + } + + for _, shr := range shrs { + if shr.ShareMode == string(sdk.PublicShareMode) && shr.BackendMode != string(sdk.DriveBackendMode) { + cfgZId, shrCfg, err := zrokEdgeSdk.GetConfig(shr.Token, edge) + if err != nil { + logrus.Errorf("error getting config for share '%v': %v", shr.Token, err) + return admin.NewGrantsInternalServerError() + } + + if shrCfg.Interstitial != !acctSkipInterstitial { + logrus.Infof("updating config for '%v'", shr.Token) + err := zrokEdgeSdk.UpdateConfig(cfgZId, shrCfg, edge) + if err != nil { + logrus.Errorf("error updating config for '%v': %v", shr.Token, err) + return admin.NewGrantsInternalServerError() + } + } else { + logrus.Infof("skipping config update for '%v'", shr.Token) + } + } else { + logrus.Debugf("skipping share mode %v, backend mode %v", shr.ShareMode, shr.BackendMode) + } + } + } + return admin.NewGrantsOK() } diff --git a/controller/zrokEdgeSdk/config.go b/controller/zrokEdgeSdk/config.go index d58f32eeb..24e8d46d7 100644 --- a/controller/zrokEdgeSdk/config.go +++ b/controller/zrokEdgeSdk/config.go @@ -8,6 +8,7 @@ import ( "github.com/openziti/edge-api/rest_model" "github.com/openziti/zrok/sdk/golang/sdk" "github.com/sirupsen/logrus" + "reflect" "time" ) @@ -55,6 +56,41 @@ func CreateConfig(cfgTypeZId, envZId, shrToken string, options *FrontendOptions, return cfgResp.Payload.Data.ID, nil } +func GetConfig(shrToken string, edge *rest_management_api_client.ZitiEdgeManagement) (string, *sdk.FrontendConfig, error) { + filter := fmt.Sprintf("tags.zrokShareToken=\"%v\"", shrToken) + limit := int64(0) + offset := int64(0) + listReq := &config.ListConfigsParams{ + Filter: &filter, + Limit: &limit, + Offset: &offset, + Context: context.Background(), + } + listReq.SetTimeout(30 * time.Second) + listResp, err := edge.Config.ListConfigs(listReq, nil) + if err != nil { + return "", nil, err + } + if len(listResp.Payload.Data) != 1 { + return "", nil, fmt.Errorf("expected 1 configuration, found %v", len(listResp.Payload.Data)) + } + if listResp.Payload.Data[0].ConfigType.Name != sdk.ZrokProxyConfig { + return "", nil, fmt.Errorf("expected '%v', found '%v'", sdk.ZrokProxyConfig, listResp.Payload.Data[0].ConfigType.Name) + } + if v, ok := listResp.Payload.Data[0].Data.(map[string]interface{}); ok { + fec, err := sdk.FrontendConfigFromMap(v) + if err != nil { + return "", nil, err + } + return *listResp.Payload.Data[0].ID, fec, nil + } + return "", nil, fmt.Errorf("unknown data type '%v' unmarshaling config for '%v'", reflect.TypeOf(listResp.Payload.Data[0].Data), shrToken) +} + +func UpdateConfig(cfgZId string, cfg *sdk.FrontendConfig, edge *rest_management_api_client.ZitiEdgeManagement) error { + return nil +} + func DeleteConfig(envZId, shrToken string, edge *rest_management_api_client.ZitiEdgeManagement) error { filter := fmt.Sprintf("tags.zrokShareToken=\"%v\"", shrToken) limit := int64(0) diff --git a/rest_client_zrok/admin/grants_responses.go b/rest_client_zrok/admin/grants_responses.go index 79354d887..a8b1ddb1b 100644 --- a/rest_client_zrok/admin/grants_responses.go +++ b/rest_client_zrok/admin/grants_responses.go @@ -34,6 +34,12 @@ func (o *GrantsReader) ReadResponse(response runtime.ClientResponse, consumer ru return nil, err } return nil, result + case 404: + result := NewGrantsNotFound() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return nil, result case 500: result := NewGrantsInternalServerError() if err := result.readResponse(response, consumer, o.formats); err != nil { @@ -157,6 +163,62 @@ func (o *GrantsUnauthorized) readResponse(response runtime.ClientResponse, consu return nil } +// NewGrantsNotFound creates a GrantsNotFound with default headers values +func NewGrantsNotFound() *GrantsNotFound { + return &GrantsNotFound{} +} + +/* +GrantsNotFound describes a response with status code 404, with default header values. + +not found +*/ +type GrantsNotFound struct { +} + +// IsSuccess returns true when this grants not found response has a 2xx status code +func (o *GrantsNotFound) IsSuccess() bool { + return false +} + +// IsRedirect returns true when this grants not found response has a 3xx status code +func (o *GrantsNotFound) IsRedirect() bool { + return false +} + +// IsClientError returns true when this grants not found response has a 4xx status code +func (o *GrantsNotFound) IsClientError() bool { + return true +} + +// IsServerError returns true when this grants not found response has a 5xx status code +func (o *GrantsNotFound) IsServerError() bool { + return false +} + +// IsCode returns true when this grants not found response a status code equal to that given +func (o *GrantsNotFound) IsCode(code int) bool { + return code == 404 +} + +// Code gets the status code for the grants not found response +func (o *GrantsNotFound) Code() int { + return 404 +} + +func (o *GrantsNotFound) Error() string { + return fmt.Sprintf("[POST /grants][%d] grantsNotFound ", 404) +} + +func (o *GrantsNotFound) String() string { + return fmt.Sprintf("[POST /grants][%d] grantsNotFound ", 404) +} + +func (o *GrantsNotFound) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + return nil +} + // NewGrantsInternalServerError creates a GrantsInternalServerError with default headers values func NewGrantsInternalServerError() *GrantsInternalServerError { return &GrantsInternalServerError{} diff --git a/rest_server_zrok/embedded_spec.go b/rest_server_zrok/embedded_spec.go index ed565e454..efc2bb0c8 100644 --- a/rest_server_zrok/embedded_spec.go +++ b/rest_server_zrok/embedded_spec.go @@ -557,6 +557,9 @@ func init() { "401": { "description": "unauthorized" }, + "404": { + "description": "not found" + }, "500": { "description": "internal server error" } @@ -2404,6 +2407,9 @@ func init() { "401": { "description": "unauthorized" }, + "404": { + "description": "not found" + }, "500": { "description": "internal server error" } diff --git a/rest_server_zrok/operations/admin/grants_responses.go b/rest_server_zrok/operations/admin/grants_responses.go index 37fd7af1f..f7682f7ab 100644 --- a/rest_server_zrok/operations/admin/grants_responses.go +++ b/rest_server_zrok/operations/admin/grants_responses.go @@ -61,6 +61,31 @@ func (o *GrantsUnauthorized) WriteResponse(rw http.ResponseWriter, producer runt rw.WriteHeader(401) } +// GrantsNotFoundCode is the HTTP code returned for type GrantsNotFound +const GrantsNotFoundCode int = 404 + +/* +GrantsNotFound not found + +swagger:response grantsNotFound +*/ +type GrantsNotFound struct { +} + +// NewGrantsNotFound creates GrantsNotFound with default headers values +func NewGrantsNotFound() *GrantsNotFound { + + return &GrantsNotFound{} +} + +// WriteResponse to the client +func (o *GrantsNotFound) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + + rw.WriteHeader(404) +} + // GrantsInternalServerErrorCode is the HTTP code returned for type GrantsInternalServerError const GrantsInternalServerErrorCode int = 500 diff --git a/sdk/golang/sdk/config.go b/sdk/golang/sdk/config.go index e07000bbb..f701d5f7b 100644 --- a/sdk/golang/sdk/config.go +++ b/sdk/golang/sdk/config.go @@ -2,6 +2,8 @@ package sdk import ( "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "reflect" ) const ZrokProxyConfig = "zrok.proxy.v1" @@ -13,21 +15,133 @@ type FrontendConfig struct { OauthAuth *OauthConfig `json:"oauth"` } +func FrontendConfigFromMap(m map[string]interface{}) (*FrontendConfig, error) { + logrus.Info(m) + out := &FrontendConfig{} + if v, found := m["interstitial"]; found { + out.Interstitial = v.(bool) + } + if v, found := m["auth_scheme"]; found { + if vStr, ok := v.(string); ok { + out.AuthScheme = AuthScheme(vStr) + } else { + return nil, errors.Errorf("unexpected type '%v'", reflect.TypeOf(v)) + } + } + if v, found := m["basic_auth"]; found && v != nil { + if subMap, ok := v.(map[string]interface{}); ok { + ba, err := BasicAuthConfigFromMap(subMap) + if err != nil { + return nil, err + } + out.BasicAuth = ba + } else { + return nil, errors.Errorf("unexpected type '%v'", reflect.TypeOf(v)) + } + } + if v, found := m["oauth"]; found && v != nil { + if subMap, ok := v.(map[string]interface{}); ok { + o, err := OauthConfigFromMap(subMap) + if err != nil { + return nil, err + } + out.OauthAuth = o + } else { + return nil, errors.Errorf("unexpected type '%v'", reflect.TypeOf(v)) + } + } + return out, nil +} + type BasicAuthConfig struct { Users []*AuthUserConfig `json:"users"` } +func BasicAuthConfigFromMap(m map[string]interface{}) (*BasicAuthConfig, error) { + out := &BasicAuthConfig{} + if v, found := m["basic_auth"]; found { + if vArr, ok := v.([]interface{}); ok { + for _, vV := range vArr { + if v, ok := vV.(map[string]interface{}); ok { + if auc, err := AuthUserConfigFromMap(v); err == nil { + out.Users = append(out.Users, auc) + } else { + return nil, err + } + } else { + return nil, errors.Errorf("unexpected type '%v'", reflect.TypeOf(v)) + } + } + } else { + return nil, errors.Errorf("unexpected type '%v'", reflect.TypeOf(v)) + } + return out, nil + } + return nil, nil +} + type AuthUserConfig struct { Username string `json:"username"` Password string `json:"password"` } +func AuthUserConfigFromMap(m map[string]interface{}) (*AuthUserConfig, error) { + auc := &AuthUserConfig{} + if v, found := m["username"]; found { + if vStr, ok := v.(string); ok { + auc.Username = vStr + } else { + return nil, errors.Errorf("unexpected type '%v'", reflect.TypeOf(v)) + } + } + if v, found := m["password"]; found { + if vStr, ok := v.(string); ok { + auc.Password = vStr + } else { + return nil, errors.Errorf("unexpected type '%v'", reflect.TypeOf(v)) + } + } + return auc, nil +} + type OauthConfig struct { Provider string `json:"provider"` EmailDomains []string `json:"email_domains"` AuthorizationCheckInterval string `json:"authorization_check_interval"` } +func OauthConfigFromMap(m map[string]interface{}) (*OauthConfig, error) { + oac := &OauthConfig{} + if v, found := m["provider"]; found { + if vStr, ok := v.(string); ok { + oac.Provider = vStr + } else { + return nil, errors.Errorf("unexpected type '%v'", reflect.TypeOf(v)) + } + } + if v, found := m["email_domains"]; found { + if vArr, ok := v.([]interface{}); ok { + for _, vV := range vArr { + if vStr, ok := vV.(string); ok { + oac.EmailDomains = append(oac.EmailDomains, vStr) + } else { + return nil, errors.Errorf("unexpected type '%v'", reflect.TypeOf(vV)) + } + } + } else { + return nil, errors.Errorf("unexpected type '%v'", reflect.TypeOf(v)) + } + } + if v, found := m["authorization_check_interval"]; found { + if vStr, ok := v.(string); ok { + oac.AuthorizationCheckInterval = vStr + } else { + return nil, errors.Errorf("unexpected type '%v'", reflect.TypeOf(v)) + } + } + return oac, nil +} + func ParseAuthScheme(authScheme string) (AuthScheme, error) { switch authScheme { case string(None): diff --git a/specs/zrok.yml b/specs/zrok.yml index cc9ef0f87..853d10592 100644 --- a/specs/zrok.yml +++ b/specs/zrok.yml @@ -326,6 +326,8 @@ paths: description: ok 401: description: unauthorized + 404: + description: not found 500: description: internal server error From b7b7b8b964fd0d69be106f617791a39fdd7ff1ea Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Fri, 6 Sep 2024 11:36:34 -0400 Subject: [PATCH 7/8] full wiring; unit testing (#744) --- controller/grants.go | 4 +- controller/zrokEdgeSdk/config.go | 17 +++++++- sdk/golang/sdk/config.go | 15 +++---- sdk/golang/sdk/config_test.go | 71 ++++++++++++++++++++++++++++++++ 4 files changed, 97 insertions(+), 10 deletions(-) create mode 100644 sdk/golang/sdk/config_test.go diff --git a/controller/grants.go b/controller/grants.go index 7bc3853a5..94cfed4ab 100644 --- a/controller/grants.go +++ b/controller/grants.go @@ -67,8 +67,8 @@ func (h *grantsHandler) Handle(params admin.GrantsParams, principal *rest_model_ } if shrCfg.Interstitial != !acctSkipInterstitial { - logrus.Infof("updating config for '%v'", shr.Token) - err := zrokEdgeSdk.UpdateConfig(cfgZId, shrCfg, edge) + shrCfg.Interstitial = !acctSkipInterstitial + err := zrokEdgeSdk.UpdateConfig(shr.Token, cfgZId, shrCfg, edge) if err != nil { logrus.Errorf("error updating config for '%v': %v", shr.Token, err) return admin.NewGrantsInternalServerError() diff --git a/controller/zrokEdgeSdk/config.go b/controller/zrokEdgeSdk/config.go index 24e8d46d7..6c9b7b8c0 100644 --- a/controller/zrokEdgeSdk/config.go +++ b/controller/zrokEdgeSdk/config.go @@ -87,7 +87,22 @@ func GetConfig(shrToken string, edge *rest_management_api_client.ZitiEdgeManagem return "", nil, fmt.Errorf("unknown data type '%v' unmarshaling config for '%v'", reflect.TypeOf(listResp.Payload.Data[0].Data), shrToken) } -func UpdateConfig(cfgZId string, cfg *sdk.FrontendConfig, edge *rest_management_api_client.ZitiEdgeManagement) error { +func UpdateConfig(shrToken, cfgZId string, cfg *sdk.FrontendConfig, edge *rest_management_api_client.ZitiEdgeManagement) error { + logrus.Infof("updating config for '%v' (%v)", shrToken, cfgZId) + req := &config.UpdateConfigParams{ + Config: &rest_model.ConfigUpdate{ + Data: cfg, + Name: &shrToken, + Tags: ZrokShareTags(shrToken), + }, + ID: cfgZId, + Context: context.Background(), + } + req.SetTimeout(30 * time.Second) + _, err := edge.Config.UpdateConfig(req, nil) + if err != nil { + return err + } return nil } diff --git a/sdk/golang/sdk/config.go b/sdk/golang/sdk/config.go index f701d5f7b..cd9b3a532 100644 --- a/sdk/golang/sdk/config.go +++ b/sdk/golang/sdk/config.go @@ -59,11 +59,11 @@ type BasicAuthConfig struct { func BasicAuthConfigFromMap(m map[string]interface{}) (*BasicAuthConfig, error) { out := &BasicAuthConfig{} - if v, found := m["basic_auth"]; found { - if vArr, ok := v.([]interface{}); ok { - for _, vV := range vArr { - if v, ok := vV.(map[string]interface{}); ok { - if auc, err := AuthUserConfigFromMap(v); err == nil { + if v, found := m["users"]; found { + if subArr, ok := v.([]interface{}); ok { + for _, v := range subArr { + if subMap, ok := v.(map[string]interface{}); ok { + if auc, err := AuthUserConfigFromMap(subMap); err == nil { out.Users = append(out.Users, auc) } else { return nil, err @@ -75,9 +75,10 @@ func BasicAuthConfigFromMap(m map[string]interface{}) (*BasicAuthConfig, error) } else { return nil, errors.Errorf("unexpected type '%v'", reflect.TypeOf(v)) } - return out, nil + } else { + return nil, errors.New("missing 'users' field") } - return nil, nil + return out, nil } type AuthUserConfig struct { diff --git a/sdk/golang/sdk/config_test.go b/sdk/golang/sdk/config_test.go new file mode 100644 index 000000000..288f585d9 --- /dev/null +++ b/sdk/golang/sdk/config_test.go @@ -0,0 +1,71 @@ +package sdk + +import ( + "encoding/json" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestBasicFrontendConfigFromMap(t *testing.T) { + inFec := &FrontendConfig{ + Interstitial: true, + AuthScheme: None, + } + m, err := frontendConfigToMap(inFec) + assert.NoError(t, err) + assert.NotNil(t, m) + outFec, err := FrontendConfigFromMap(m) + assert.NoError(t, err) + assert.NotNil(t, outFec) + assert.Equal(t, inFec, outFec) +} + +func TestBasicAuthFrontendConfigFromMap(t *testing.T) { + inFec := &FrontendConfig{ + Interstitial: false, + AuthScheme: Basic, + BasicAuth: &BasicAuthConfig{ + Users: []*AuthUserConfig{ + {Username: "nobody", Password: "password"}, + }, + }, + } + m, err := frontendConfigToMap(inFec) + assert.NoError(t, err) + assert.NotNil(t, m) + outFec, err := FrontendConfigFromMap(m) + assert.NoError(t, err) + assert.NotNil(t, outFec) + assert.Equal(t, inFec, outFec) +} + +func TestOauthAuthFrontendConfigFromMap(t *testing.T) { + inFec := &FrontendConfig{ + Interstitial: true, + AuthScheme: Oauth, + OauthAuth: &OauthConfig{ + Provider: "google", + EmailDomains: []string{"a@b.com", "c@d.com"}, + AuthorizationCheckInterval: "5m", + }, + } + m, err := frontendConfigToMap(inFec) + assert.NoError(t, err) + assert.NotNil(t, m) + outFec, err := FrontendConfigFromMap(m) + assert.NoError(t, err) + assert.NotNil(t, outFec) + assert.Equal(t, inFec, outFec) +} + +func frontendConfigToMap(fec *FrontendConfig) (map[string]interface{}, error) { + jsonData, err := json.Marshal(fec) + if err != nil { + return nil, err + } + m := make(map[string]interface{}) + if err := json.Unmarshal(jsonData, &m); err != nil { + return nil, err + } + return m, nil +} From 5d452daa3c9929424003181cc8878e0b358c5036 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Fri, 6 Sep 2024 11:44:01 -0400 Subject: [PATCH 8/8] lint (#744) --- sdk/golang/sdk/config.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/sdk/golang/sdk/config.go b/sdk/golang/sdk/config.go index cd9b3a532..5d41c5527 100644 --- a/sdk/golang/sdk/config.go +++ b/sdk/golang/sdk/config.go @@ -2,7 +2,6 @@ package sdk import ( "github.com/pkg/errors" - "github.com/sirupsen/logrus" "reflect" ) @@ -16,7 +15,6 @@ type FrontendConfig struct { } func FrontendConfigFromMap(m map[string]interface{}) (*FrontendConfig, error) { - logrus.Info(m) out := &FrontendConfig{} if v, found := m["interstitial"]; found { out.Interstitial = v.(bool)