Add trials registration client

This commit is contained in:
abhisek 2023-02-01 14:27:01 +05:30
parent 0405d9d83d
commit 8b3c026deb
No known key found for this signature in database
GPG Key ID: CB92A4990C02A88F
8 changed files with 685 additions and 9 deletions

View File

@ -10,9 +10,11 @@ oapi-codegen-install:
oapi-codegen:
oapi-codegen -package insightapi -generate types ./api/insights-v1.yml > ./gen/insightapi/insights.types.go
oapi-codegen -package insightapi -generate client ./api/insights-v1.yml > ./gen/insightapi/insights.client.go
oapi-codegen -package controlplane -generate types ./api/cp-v1-trials.yml > ./gen/controlplane/trials.types.go
oapi-codegen -package controlplane -generate client ./api/cp-v1-trials.yml > ./gen/controlplane/trials.client.go
setup:
mkdir -p out gen/insightapi
mkdir -p out gen/insightapi gen/controlplane
GO_CFLAGS=-X main.commit=$(GITCOMMIT) -X main.version=$(VERSION)
GO_LDFLAGS=-ldflags "-w $(GO_CFLAGS)"

123
api/cp-v1-trials.yml Normal file
View File

@ -0,0 +1,123 @@
openapi: 3.0.2
info:
title: SafeDep Control Plane API for Trials Registration
contact:
name: SafeDep API
url: 'https://safedep.io'
description: |
Trials API provide a way for obtaining an API Key for data plane service access
using an Email Address. Trials is different from Registrations as the later
allows full access to the control plane while Trials is meant to allow access
only to a time bounded (expirable) API key for quick evaluation of tools.
version: 0.0.1
servers:
- url: 'https://{apiHost}/{apiBase}'
variables:
apiHost:
default: api.safedep.io
apiBase:
default: control-plane/v1
tags:
- name: Control Plane
description: Control Plane API
paths:
/trials:
post:
description: |
Register a trial user to obtain an expirable API Key. The API key will
be generated and sent to the user over Email to ensure validity and access
to the email by the requester. System defined limits will be applied to
maximum number of trial API keys that can be generated for an email.
operationId: registerTrialUser
tags:
- Control Plane
requestBody:
description: Trial registration request
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/TrialRequest'
responses:
'200':
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/TrialResponse'
'403':
description: Access to the API is denied
content:
application/json:
schema:
$ref: '#/components/schemas/ApiError'
'429':
description: Rate limit block
content:
application/json:
schema:
$ref: '#/components/schemas/ApiError'
'500':
description: Failed due to internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/ApiError'
components:
schemas:
ApiError:
type: object
properties:
message:
type: string
description: A descriptive message about the error meant for developer consumption
type:
type: string
description: An optional service or domain specific error group
enum:
- invalid_request
- operation_failed
- internal_error
code:
type: string
description: An error code identifying the error
enum:
- api_guard_invalid_credentials
- api_guard_rate_limit_exceeded
- api_guard_unauthorized
- api_guard_error
- app_generic_error
- app_security_error
- app_insufficient_parameters
- app_feature_not_enabled
- app_package_version_not_found
params:
type: object
description: Optional error specific attributes
additionalProperties:
type: object
properties:
key:
type: string
value:
type: string
TrialRequest:
type: object
properties:
email:
type: string
format: email
required:
- email
TrialResponse:
type: object
properties:
id:
type: string
minLength: 6
maxLength: 512
description: The ID of the trial registration request created in the system
expires_at:
type: string
format: date-time
description: The expiry time of the API key

46
auth.go
View File

@ -12,7 +12,9 @@ import (
)
var (
authInsightApiBaseUrl string
authInsightApiBaseUrl string
authControlPlaneApiBaseUrl string
authTrialEmail string
)
func newAuthCommand() *cobra.Command {
@ -28,6 +30,7 @@ func newAuthCommand() *cobra.Command {
cmd.AddCommand(configureAuthCommand())
cmd.AddCommand(verifyAuthCommand())
cmd.AddCommand(trialsRegisterCommand())
return cmd
}
@ -43,8 +46,9 @@ func configureAuthCommand() *cobra.Command {
}
err = auth.Configure(auth.Config{
ApiUrl: authInsightApiBaseUrl,
ApiKey: string(key),
ApiUrl: authInsightApiBaseUrl,
ApiKey: string(key),
ControlPlaneApiUrl: authControlPlaneApiBaseUrl,
})
if err != nil {
panic(err)
@ -55,8 +59,10 @@ func configureAuthCommand() *cobra.Command {
},
}
cmd.Flags().StringVarP(&authInsightApiBaseUrl, "api", "", "https://api.safedep.io/insights/v1",
cmd.Flags().StringVarP(&authInsightApiBaseUrl, "api", "", auth.DefaultApiUrl(),
"Base URL of Insights API")
cmd.Flags().StringVarP(&authControlPlaneApiBaseUrl, "control-plane", "",
auth.DefaultControlPlaneApiUrl(), "Base URL of Control Plane API for registrations")
return cmd
@ -66,7 +72,7 @@ func verifyAuthCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "verify",
RunE: func(cmd *cobra.Command, args []string) error {
// Run auth.Verify()
fmt.Printf("Verify auth command is currently work in progress\n")
os.Exit(1)
return nil
},
@ -74,3 +80,33 @@ func verifyAuthCommand() *cobra.Command {
return cmd
}
func trialsRegisterCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "trial",
RunE: func(cmd *cobra.Command, args []string) error {
client := auth.NewTrialRegistrationClient(auth.TrialConfig{
Email: authTrialEmail,
ControlPlaneApiUrl: authControlPlaneApiBaseUrl,
})
res, err := client.Execute()
if err != nil {
return err
}
fmt.Printf("Trial registration successful with Id:%s\n", res.Id)
fmt.Printf("Check your email (%s) for API key and usage instructions\n", authTrialEmail)
fmt.Printf("The trial API key will expire on %s\n", res.ExpiresAt.String())
return nil
},
}
cmd.Flags().StringVarP(&authTrialEmail, "email", "", "",
"Email address to use for sending trial API key")
cmd.Flags().StringVarP(&authControlPlaneApiBaseUrl, "control-plane", "",
auth.DefaultControlPlaneApiUrl(), "Base URL of Control Plane API for registrations")
return cmd
}

View File

@ -0,0 +1,297 @@
// Package controlplane provides primitives to interact with the openapi HTTP API.
//
// Code generated by github.com/deepmap/oapi-codegen version v1.10.1 DO NOT EDIT.
package controlplane
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
)
// RequestEditorFn is the function signature for the RequestEditor callback function
type RequestEditorFn func(ctx context.Context, req *http.Request) error
// Doer performs HTTP requests.
//
// The standard http.Client implements this interface.
type HttpRequestDoer interface {
Do(req *http.Request) (*http.Response, error)
}
// Client which conforms to the OpenAPI3 specification for this service.
type Client struct {
// The endpoint of the server conforming to this interface, with scheme,
// https://api.deepmap.com for example. This can contain a path relative
// to the server, such as https://api.deepmap.com/dev-test, and all the
// paths in the swagger spec will be appended to the server.
Server string
// Doer for performing requests, typically a *http.Client with any
// customized settings, such as certificate chains.
Client HttpRequestDoer
// A list of callbacks for modifying requests which are generated before sending over
// the network.
RequestEditors []RequestEditorFn
}
// ClientOption allows setting custom parameters during construction
type ClientOption func(*Client) error
// Creates a new Client, with reasonable defaults
func NewClient(server string, opts ...ClientOption) (*Client, error) {
// create a client with sane default values
client := Client{
Server: server,
}
// mutate client and add all optional params
for _, o := range opts {
if err := o(&client); err != nil {
return nil, err
}
}
// ensure the server URL always has a trailing slash
if !strings.HasSuffix(client.Server, "/") {
client.Server += "/"
}
// create httpClient, if not already present
if client.Client == nil {
client.Client = &http.Client{}
}
return &client, nil
}
// WithHTTPClient allows overriding the default Doer, which is
// automatically created using http.Client. This is useful for tests.
func WithHTTPClient(doer HttpRequestDoer) ClientOption {
return func(c *Client) error {
c.Client = doer
return nil
}
}
// WithRequestEditorFn allows setting up a callback function, which will be
// called right before sending the request. This can be used to mutate the request.
func WithRequestEditorFn(fn RequestEditorFn) ClientOption {
return func(c *Client) error {
c.RequestEditors = append(c.RequestEditors, fn)
return nil
}
}
// The interface specification for the client above.
type ClientInterface interface {
// RegisterTrialUser request with any body
RegisterTrialUserWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
RegisterTrialUser(ctx context.Context, body RegisterTrialUserJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)
}
func (c *Client) RegisterTrialUserWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewRegisterTrialUserRequestWithBody(c.Server, contentType, body)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
return nil, err
}
return c.Client.Do(req)
}
func (c *Client) RegisterTrialUser(ctx context.Context, body RegisterTrialUserJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewRegisterTrialUserRequest(c.Server, body)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
return nil, err
}
return c.Client.Do(req)
}
// NewRegisterTrialUserRequest calls the generic RegisterTrialUser builder with application/json body
func NewRegisterTrialUserRequest(server string, body RegisterTrialUserJSONRequestBody) (*http.Request, error) {
var bodyReader io.Reader
buf, err := json.Marshal(body)
if err != nil {
return nil, err
}
bodyReader = bytes.NewReader(buf)
return NewRegisterTrialUserRequestWithBody(server, "application/json", bodyReader)
}
// NewRegisterTrialUserRequestWithBody generates requests for RegisterTrialUser with any type of body
func NewRegisterTrialUserRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) {
var err error
serverURL, err := url.Parse(server)
if err != nil {
return nil, err
}
operationPath := fmt.Sprintf("/trials")
if operationPath[0] == '/' {
operationPath = "." + operationPath
}
queryURL, err := serverURL.Parse(operationPath)
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", queryURL.String(), body)
if err != nil {
return nil, err
}
req.Header.Add("Content-Type", contentType)
return req, nil
}
func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error {
for _, r := range c.RequestEditors {
if err := r(ctx, req); err != nil {
return err
}
}
for _, r := range additionalEditors {
if err := r(ctx, req); err != nil {
return err
}
}
return nil
}
// ClientWithResponses builds on ClientInterface to offer response payloads
type ClientWithResponses struct {
ClientInterface
}
// NewClientWithResponses creates a new ClientWithResponses, which wraps
// Client with return type handling
func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) {
client, err := NewClient(server, opts...)
if err != nil {
return nil, err
}
return &ClientWithResponses{client}, nil
}
// WithBaseURL overrides the baseURL.
func WithBaseURL(baseURL string) ClientOption {
return func(c *Client) error {
newBaseURL, err := url.Parse(baseURL)
if err != nil {
return err
}
c.Server = newBaseURL.String()
return nil
}
}
// ClientWithResponsesInterface is the interface specification for the client with responses above.
type ClientWithResponsesInterface interface {
// RegisterTrialUser request with any body
RegisterTrialUserWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RegisterTrialUserResponse, error)
RegisterTrialUserWithResponse(ctx context.Context, body RegisterTrialUserJSONRequestBody, reqEditors ...RequestEditorFn) (*RegisterTrialUserResponse, error)
}
type RegisterTrialUserResponse struct {
Body []byte
HTTPResponse *http.Response
JSON200 *TrialResponse
JSON403 *ApiError
JSON429 *ApiError
JSON500 *ApiError
}
// Status returns HTTPResponse.Status
func (r RegisterTrialUserResponse) Status() string {
if r.HTTPResponse != nil {
return r.HTTPResponse.Status
}
return http.StatusText(0)
}
// StatusCode returns HTTPResponse.StatusCode
func (r RegisterTrialUserResponse) StatusCode() int {
if r.HTTPResponse != nil {
return r.HTTPResponse.StatusCode
}
return 0
}
// RegisterTrialUserWithBodyWithResponse request with arbitrary body returning *RegisterTrialUserResponse
func (c *ClientWithResponses) RegisterTrialUserWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RegisterTrialUserResponse, error) {
rsp, err := c.RegisterTrialUserWithBody(ctx, contentType, body, reqEditors...)
if err != nil {
return nil, err
}
return ParseRegisterTrialUserResponse(rsp)
}
func (c *ClientWithResponses) RegisterTrialUserWithResponse(ctx context.Context, body RegisterTrialUserJSONRequestBody, reqEditors ...RequestEditorFn) (*RegisterTrialUserResponse, error) {
rsp, err := c.RegisterTrialUser(ctx, body, reqEditors...)
if err != nil {
return nil, err
}
return ParseRegisterTrialUserResponse(rsp)
}
// ParseRegisterTrialUserResponse parses an HTTP response from a RegisterTrialUserWithResponse call
func ParseRegisterTrialUserResponse(rsp *http.Response) (*RegisterTrialUserResponse, error) {
bodyBytes, err := ioutil.ReadAll(rsp.Body)
defer func() { _ = rsp.Body.Close() }()
if err != nil {
return nil, err
}
response := &RegisterTrialUserResponse{
Body: bodyBytes,
HTTPResponse: rsp,
}
switch {
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
var dest TrialResponse
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON200 = &dest
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403:
var dest ApiError
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON403 = &dest
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429:
var dest ApiError
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON429 = &dest
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500:
var dest ApiError
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON500 = &dest
}
return response, nil
}

View File

@ -0,0 +1,159 @@
// Package controlplane provides primitives to interact with the openapi HTTP API.
//
// Code generated by github.com/deepmap/oapi-codegen version v1.10.1 DO NOT EDIT.
package controlplane
import (
"encoding/json"
"fmt"
"time"
openapi_types "github.com/deepmap/oapi-codegen/pkg/types"
)
// Defines values for ApiErrorCode.
const (
ApiErrorCodeApiGuardError ApiErrorCode = "api_guard_error"
ApiErrorCodeApiGuardInvalidCredentials ApiErrorCode = "api_guard_invalid_credentials"
ApiErrorCodeApiGuardRateLimitExceeded ApiErrorCode = "api_guard_rate_limit_exceeded"
ApiErrorCodeApiGuardUnauthorized ApiErrorCode = "api_guard_unauthorized"
ApiErrorCodeAppFeatureNotEnabled ApiErrorCode = "app_feature_not_enabled"
ApiErrorCodeAppGenericError ApiErrorCode = "app_generic_error"
ApiErrorCodeAppInsufficientParameters ApiErrorCode = "app_insufficient_parameters"
ApiErrorCodeAppPackageVersionNotFound ApiErrorCode = "app_package_version_not_found"
ApiErrorCodeAppSecurityError ApiErrorCode = "app_security_error"
)
// Defines values for ApiErrorType.
const (
ApiErrorTypeInternalError ApiErrorType = "internal_error"
ApiErrorTypeInvalidRequest ApiErrorType = "invalid_request"
ApiErrorTypeOperationFailed ApiErrorType = "operation_failed"
)
// ApiError defines model for ApiError.
type ApiError struct {
// An error code identifying the error
Code *ApiErrorCode `json:"code,omitempty"`
// A descriptive message about the error meant for developer consumption
Message *string `json:"message,omitempty"`
// Optional error specific attributes
Params *ApiError_Params `json:"params,omitempty"`
// An optional service or domain specific error group
Type *ApiErrorType `json:"type,omitempty"`
}
// An error code identifying the error
type ApiErrorCode string
// Optional error specific attributes
type ApiError_Params struct {
AdditionalProperties map[string]struct {
Key *string `json:"key,omitempty"`
Value *string `json:"value,omitempty"`
} `json:"-"`
}
// An optional service or domain specific error group
type ApiErrorType string
// TrialRequest defines model for TrialRequest.
type TrialRequest struct {
Email openapi_types.Email `json:"email"`
}
// TrialResponse defines model for TrialResponse.
type TrialResponse struct {
// The expiry time of the API key
ExpiresAt *time.Time `json:"expires_at,omitempty"`
// The ID of the trial registration request created in the system
Id *string `json:"id,omitempty"`
}
// RegisterTrialUserJSONBody defines parameters for RegisterTrialUser.
type RegisterTrialUserJSONBody TrialRequest
// RegisterTrialUserJSONRequestBody defines body for RegisterTrialUser for application/json ContentType.
type RegisterTrialUserJSONRequestBody RegisterTrialUserJSONBody
// Getter for additional properties for ApiError_Params. Returns the specified
// element and whether it was found
func (a ApiError_Params) Get(fieldName string) (value struct {
Key *string `json:"key,omitempty"`
Value *string `json:"value,omitempty"`
}, found bool) {
if a.AdditionalProperties != nil {
value, found = a.AdditionalProperties[fieldName]
}
return
}
// Setter for additional properties for ApiError_Params
func (a *ApiError_Params) Set(fieldName string, value struct {
Key *string `json:"key,omitempty"`
Value *string `json:"value,omitempty"`
}) {
if a.AdditionalProperties == nil {
a.AdditionalProperties = make(map[string]struct {
Key *string `json:"key,omitempty"`
Value *string `json:"value,omitempty"`
})
}
a.AdditionalProperties[fieldName] = value
}
// Override default JSON handling for ApiError_Params to handle AdditionalProperties
func (a *ApiError_Params) UnmarshalJSON(b []byte) error {
object := make(map[string]json.RawMessage)
err := json.Unmarshal(b, &object)
if err != nil {
return err
}
if len(object) != 0 {
a.AdditionalProperties = make(map[string]struct {
Key *string `json:"key,omitempty"`
Value *string `json:"value,omitempty"`
})
for fieldName, fieldBuf := range object {
var fieldVal struct {
Key *string `json:"key,omitempty"`
Value *string `json:"value,omitempty"`
}
err := json.Unmarshal(fieldBuf, &fieldVal)
if err != nil {
return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err)
}
a.AdditionalProperties[fieldName] = fieldVal
}
}
return nil
}
// Override default JSON handling for ApiError_Params to handle AdditionalProperties
func (a ApiError_Params) MarshalJSON() ([]byte, error) {
var err error
object := make(map[string]json.RawMessage)
for fieldName, field := range a.AdditionalProperties {
object[fieldName], err = json.Marshal(field)
if err != nil {
return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err)
}
}
return json.Marshal(object)
}

View File

@ -13,14 +13,18 @@ const (
apiUrlEnvKey = "VET_INSIGHTS_API_URL"
apiKeyEnvKey = "VET_INSIGHTS_API_KEY"
defaultApiUrl = "https://api.safedep.io/insights/v1"
controlPlaneUrlEnvKey = "VET_CONTROL_PLANE_API_URL"
defaultApiUrl = "https://api.safedep.io/insights/v1"
defaultControlPlaneApiUrl = "https://api.safedep.io/controlplane/v1"
homeRelativeConfigPath = ".safedep/vet-auth.yml"
)
type Config struct {
ApiUrl string `yaml:"api_url"`
ApiKey string `yaml:"api_key"`
ApiUrl string `yaml:"api_url"`
ApiKey string `yaml:"api_key"`
ControlPlaneApiUrl string `yaml:"control_plane_api"`
}
// Global config to be used during runtime
@ -40,6 +44,14 @@ func Verify() error {
return nil
}
func DefaultApiUrl() string {
return defaultApiUrl
}
func DefaultControlPlaneApiUrl() string {
return defaultControlPlaneApiUrl
}
func ApiUrl() string {
if url, ok := os.LookupEnv(apiUrlEnvKey); ok {
return url
@ -64,6 +76,18 @@ func ApiKey() string {
return ""
}
func ControlPlaneApiUrl() string {
if url, ok := os.LookupEnv(controlPlaneUrlEnvKey); ok {
return url
}
if globalConfig != nil {
return globalConfig.ControlPlaneApiUrl
}
return defaultControlPlaneApiUrl
}
func loadConfiguration() error {
path, err := os.UserHomeDir()
path = filepath.Join(path, homeRelativeConfigPath)

34
internal/auth/trial.go Normal file
View File

@ -0,0 +1,34 @@
package auth
import (
"errors"
"time"
"github.com/safedep/dry/utils"
)
type TrialConfig struct {
Email string
ControlPlaneApiUrl string
}
type trialRegistrationResponse struct {
Id string
ExpiresAt time.Time
}
type trialRegistrationClient struct {
config TrialConfig
}
func NewTrialRegistrationClient(config TrialConfig) *trialRegistrationClient {
return &trialRegistrationClient{config: config}
}
func (client *trialRegistrationClient) Execute() (*trialRegistrationResponse, error) {
if utils.IsEmptyString(client.config.Email) {
return nil, errors.New("email is required")
}
return &trialRegistrationResponse{}, nil
}

View File

@ -26,6 +26,7 @@ var banner string = `
| | | || | | || | | |
| '--------------' || '--------------' || '--------------' |
'----------------' '----------------' '----------------'
`
func main() {