From 41b34645f919f1938b399b54c3aef9bdd9899493 Mon Sep 17 00:00:00 2001 From: Lenin Alevski Date: Sun, 23 Jan 2022 23:42:00 -0600 Subject: [PATCH] Subnet cluster registration (#1338) - Removed old registration flow - Add support for new online and offline cluster registration flow - Support login accounts with mfa enabled - Registration screens Signed-off-by: Lenin Alevski --- cmd/console/operator.go | 25 - go.mod | 2 +- models/subnet_login_m_f_a_request.go | 122 ++++ models/subnet_login_request.go | 73 +++ models/subnet_login_response.go | 142 ++++ models/subnet_organization.go | 82 +++ models/subnet_reg_token_response.go | 67 ++ models/subnet_register_request.go | 105 +++ operatorapi/configure_operator.go | 2 - operatorapi/operator_subscription.go | 416 ------------ operatorapi/operator_subscription_test.go | 359 ---------- operatorapi/operator_tenants.go | 19 - pkg/subnet/config.go | 12 + pkg/subnet/const.go | 5 - pkg/subnet/subnet.go | 263 +++----- pkg/subnet/subnet_test.go | 332 ---------- pkg/subnet/utils.go | 165 +++++ .../src/common/SecureComponent/permissions.ts | 9 +- .../src/icons/OfflineRegistrationBackIcon.tsx | 32 + .../src/icons/OfflineRegistrationIcon.tsx | 71 ++ .../src/icons/OnlineRegistrationBackIcon.tsx | 88 +++ .../src/icons/OnlineRegistrationIcon.tsx | 109 +++ portal-ui/src/icons/VerifiedIcon.tsx | 44 ++ .../src/screens/Console/Buckets/types.tsx | 6 - .../InputBoxWrapper/InputBoxWrapper.tsx | 8 +- .../Console/License/ActivationModal.tsx | 456 ++++++++++--- .../src/screens/Console/License/License.tsx | 122 ++-- .../src/screens/Console/License/types.tsx | 39 +- .../src/screens/Console/License/utils.ts | 4 +- .../src/screens/Console/Support/Register.tsx | 618 ++++++++++++++++++ .../Console/Tenants/ListTenants/types.ts | 4 +- .../TenantDetails/SubnetLicenseTenant.tsx | 4 +- .../Tenants/TenantDetails/TenantLicense.tsx | 6 +- portal-ui/src/screens/Console/Tools/Tools.tsx | 16 +- restapi/admin_subnet.go | 282 ++++++++ restapi/admin_subscription.go | 78 --- restapi/configure_console.go | 4 +- restapi/embedded_spec.go | 448 ++++++++++++- .../{subscription_info.go => subnet_info.go} | 30 +- ...arameters.go => subnet_info_parameters.go} | 16 +- ..._responses.go => subnet_info_responses.go} | 56 +- ...rlbuilder.go => subnet_info_urlbuilder.go} | 24 +- restapi/operations/admin_api/subnet_login.go | 88 +++ .../admin_api/subnet_login_m_f_a.go | 88 +++ .../subnet_login_m_f_a_parameters.go | 102 +++ .../admin_api/subnet_login_m_f_a_responses.go | 133 ++++ .../subnet_login_m_f_a_urlbuilder.go | 104 +++ .../admin_api/subnet_login_parameters.go | 102 +++ .../admin_api/subnet_login_responses.go | 133 ++++ .../admin_api/subnet_login_urlbuilder.go | 104 +++ .../operations/admin_api/subnet_reg_token.go | 88 +++ .../admin_api/subnet_reg_token_parameters.go | 63 ++ .../admin_api/subnet_reg_token_responses.go | 133 ++++ .../admin_api/subnet_reg_token_urlbuilder.go | 104 +++ .../operations/admin_api/subnet_register.go | 88 +++ .../admin_api/subnet_register_parameters.go | 102 +++ .../admin_api/subnet_register_responses.go | 113 ++++ .../admin_api/subnet_register_urlbuilder.go | 104 +++ restapi/operations/console_api.go | 62 +- swagger-console.yml | 178 ++++- 60 files changed, 4944 insertions(+), 1710 deletions(-) create mode 100644 models/subnet_login_m_f_a_request.go create mode 100644 models/subnet_login_request.go create mode 100644 models/subnet_login_response.go create mode 100644 models/subnet_organization.go create mode 100644 models/subnet_reg_token_response.go create mode 100644 models/subnet_register_request.go delete mode 100644 operatorapi/operator_subscription.go delete mode 100644 operatorapi/operator_subscription_test.go delete mode 100644 pkg/subnet/subnet_test.go create mode 100644 pkg/subnet/utils.go create mode 100644 portal-ui/src/icons/OfflineRegistrationBackIcon.tsx create mode 100644 portal-ui/src/icons/OfflineRegistrationIcon.tsx create mode 100644 portal-ui/src/icons/OnlineRegistrationBackIcon.tsx create mode 100644 portal-ui/src/icons/OnlineRegistrationIcon.tsx create mode 100644 portal-ui/src/icons/VerifiedIcon.tsx create mode 100644 portal-ui/src/screens/Console/Support/Register.tsx create mode 100644 restapi/admin_subnet.go delete mode 100644 restapi/admin_subscription.go rename restapi/operations/admin_api/{subscription_info.go => subnet_info.go} (63%) rename restapi/operations/admin_api/{subscription_info_parameters.go => subnet_info_parameters.go} (77%) rename restapi/operations/admin_api/{subscription_info_responses.go => subnet_info_responses.go} (51%) rename restapi/operations/admin_api/{subscription_info_urlbuilder.go => subnet_info_urlbuilder.go} (79%) create mode 100644 restapi/operations/admin_api/subnet_login.go create mode 100644 restapi/operations/admin_api/subnet_login_m_f_a.go create mode 100644 restapi/operations/admin_api/subnet_login_m_f_a_parameters.go create mode 100644 restapi/operations/admin_api/subnet_login_m_f_a_responses.go create mode 100644 restapi/operations/admin_api/subnet_login_m_f_a_urlbuilder.go create mode 100644 restapi/operations/admin_api/subnet_login_parameters.go create mode 100644 restapi/operations/admin_api/subnet_login_responses.go create mode 100644 restapi/operations/admin_api/subnet_login_urlbuilder.go create mode 100644 restapi/operations/admin_api/subnet_reg_token.go create mode 100644 restapi/operations/admin_api/subnet_reg_token_parameters.go create mode 100644 restapi/operations/admin_api/subnet_reg_token_responses.go create mode 100644 restapi/operations/admin_api/subnet_reg_token_urlbuilder.go create mode 100644 restapi/operations/admin_api/subnet_register.go create mode 100644 restapi/operations/admin_api/subnet_register_parameters.go create mode 100644 restapi/operations/admin_api/subnet_register_responses.go create mode 100644 restapi/operations/admin_api/subnet_register_urlbuilder.go diff --git a/cmd/console/operator.go b/cmd/console/operator.go index ad9ea9b31..138eb6d00 100644 --- a/cmd/console/operator.go +++ b/cmd/console/operator.go @@ -225,30 +225,5 @@ func startOperatorServer(ctx *cli.Context) error { defer server.Shutdown() - // subnet license refresh process - go func() { - // start refreshing subnet license after 5 seconds.. - time.Sleep(time.Second * 5) - - failedAttempts := 0 - for { - if err := operatorapi.RefreshLicense(); err != nil { - operatorapi.LogError("Refreshing subnet license failed: %v", err) - failedAttempts++ - // end license refresh after 3 consecutive failed attempts - if failedAttempts >= 3 { - return - } - // wait 5 minutes and retry again - time.Sleep(time.Minute * 5) - continue - } - // if license refreshed successfully reset the counter - failedAttempts = 0 - // try to refresh license every 24 hrs - time.Sleep(time.Hour * 24) - } - }() - return server.Serve() } diff --git a/go.mod b/go.mod index 34b8a5032..46bc1c943 100644 --- a/go.mod +++ b/go.mod @@ -30,6 +30,7 @@ require ( github.com/rs/xid v1.3.0 github.com/secure-io/sio-go v0.3.1 github.com/stretchr/testify v1.7.0 + github.com/tidwall/gjson v1.10.2 github.com/unrolled/secure v1.0.9 golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 @@ -118,7 +119,6 @@ require ( github.com/rjeczalik/notify v0.9.2 // indirect github.com/shirou/gopsutil/v3 v3.21.8 // indirect github.com/sirupsen/logrus v1.8.1 // indirect - github.com/tidwall/gjson v1.10.2 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect github.com/tinylib/msgp v1.1.6 // indirect diff --git a/models/subnet_login_m_f_a_request.go b/models/subnet_login_m_f_a_request.go new file mode 100644 index 000000000..936a5f553 --- /dev/null +++ b/models/subnet_login_m_f_a_request.go @@ -0,0 +1,122 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// SubnetLoginMFARequest subnet login m f a request +// +// swagger:model subnetLoginMFARequest +type SubnetLoginMFARequest struct { + + // mfa token + // Required: true + MfaToken *string `json:"mfa_token"` + + // otp + // Required: true + Otp *string `json:"otp"` + + // username + // Required: true + Username *string `json:"username"` +} + +// Validate validates this subnet login m f a request +func (m *SubnetLoginMFARequest) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateMfaToken(formats); err != nil { + res = append(res, err) + } + + if err := m.validateOtp(formats); err != nil { + res = append(res, err) + } + + if err := m.validateUsername(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *SubnetLoginMFARequest) validateMfaToken(formats strfmt.Registry) error { + + if err := validate.Required("mfa_token", "body", m.MfaToken); err != nil { + return err + } + + return nil +} + +func (m *SubnetLoginMFARequest) validateOtp(formats strfmt.Registry) error { + + if err := validate.Required("otp", "body", m.Otp); err != nil { + return err + } + + return nil +} + +func (m *SubnetLoginMFARequest) validateUsername(formats strfmt.Registry) error { + + if err := validate.Required("username", "body", m.Username); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this subnet login m f a request based on context it is used +func (m *SubnetLoginMFARequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *SubnetLoginMFARequest) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *SubnetLoginMFARequest) UnmarshalBinary(b []byte) error { + var res SubnetLoginMFARequest + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/models/subnet_login_request.go b/models/subnet_login_request.go new file mode 100644 index 000000000..13f8e0d5d --- /dev/null +++ b/models/subnet_login_request.go @@ -0,0 +1,73 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// SubnetLoginRequest subnet login request +// +// swagger:model subnetLoginRequest +type SubnetLoginRequest struct { + + // api key + APIKey string `json:"apiKey,omitempty"` + + // password + Password string `json:"password,omitempty"` + + // username + Username string `json:"username,omitempty"` +} + +// Validate validates this subnet login request +func (m *SubnetLoginRequest) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this subnet login request based on context it is used +func (m *SubnetLoginRequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *SubnetLoginRequest) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *SubnetLoginRequest) UnmarshalBinary(b []byte) error { + var res SubnetLoginRequest + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/models/subnet_login_response.go b/models/subnet_login_response.go new file mode 100644 index 000000000..8115cd19e --- /dev/null +++ b/models/subnet_login_response.go @@ -0,0 +1,142 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "strconv" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// SubnetLoginResponse subnet login response +// +// swagger:model subnetLoginResponse +type SubnetLoginResponse struct { + + // access token + AccessToken string `json:"access_token,omitempty"` + + // mfa token + MfaToken string `json:"mfa_token,omitempty"` + + // organizations + Organizations []*SubnetOrganization `json:"organizations"` + + // registered + Registered bool `json:"registered,omitempty"` +} + +// Validate validates this subnet login response +func (m *SubnetLoginResponse) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateOrganizations(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *SubnetLoginResponse) validateOrganizations(formats strfmt.Registry) error { + if swag.IsZero(m.Organizations) { // not required + return nil + } + + for i := 0; i < len(m.Organizations); i++ { + if swag.IsZero(m.Organizations[i]) { // not required + continue + } + + if m.Organizations[i] != nil { + if err := m.Organizations[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("organizations" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("organizations" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +// ContextValidate validate this subnet login response based on the context it is used +func (m *SubnetLoginResponse) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateOrganizations(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *SubnetLoginResponse) contextValidateOrganizations(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.Organizations); i++ { + + if m.Organizations[i] != nil { + if err := m.Organizations[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("organizations" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("organizations" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +// MarshalBinary interface implementation +func (m *SubnetLoginResponse) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *SubnetLoginResponse) UnmarshalBinary(b []byte) error { + var res SubnetLoginResponse + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/models/subnet_organization.go b/models/subnet_organization.go new file mode 100644 index 000000000..c94a5b197 --- /dev/null +++ b/models/subnet_organization.go @@ -0,0 +1,82 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// SubnetOrganization subnet organization +// +// swagger:model subnetOrganization +type SubnetOrganization struct { + + // account Id + AccountID int64 `json:"accountId,omitempty"` + + // company + Company string `json:"company,omitempty"` + + // is account owner + IsAccountOwner bool `json:"isAccountOwner,omitempty"` + + // short name + ShortName string `json:"shortName,omitempty"` + + // subscription status + SubscriptionStatus string `json:"subscriptionStatus,omitempty"` + + // user Id + UserID int64 `json:"userId,omitempty"` +} + +// Validate validates this subnet organization +func (m *SubnetOrganization) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this subnet organization based on context it is used +func (m *SubnetOrganization) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *SubnetOrganization) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *SubnetOrganization) UnmarshalBinary(b []byte) error { + var res SubnetOrganization + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/models/subnet_reg_token_response.go b/models/subnet_reg_token_response.go new file mode 100644 index 000000000..1da0bfeb3 --- /dev/null +++ b/models/subnet_reg_token_response.go @@ -0,0 +1,67 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// SubnetRegTokenResponse subnet reg token response +// +// swagger:model SubnetRegTokenResponse +type SubnetRegTokenResponse struct { + + // reg token + RegToken string `json:"regToken,omitempty"` +} + +// Validate validates this subnet reg token response +func (m *SubnetRegTokenResponse) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this subnet reg token response based on context it is used +func (m *SubnetRegTokenResponse) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *SubnetRegTokenResponse) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *SubnetRegTokenResponse) UnmarshalBinary(b []byte) error { + var res SubnetRegTokenResponse + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/models/subnet_register_request.go b/models/subnet_register_request.go new file mode 100644 index 000000000..4ab3e339d --- /dev/null +++ b/models/subnet_register_request.go @@ -0,0 +1,105 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// SubnetRegisterRequest subnet register request +// +// swagger:model subnetRegisterRequest +type SubnetRegisterRequest struct { + + // account id + // Required: true + AccountID *string `json:"account_id"` + + // token + // Required: true + Token *string `json:"token"` +} + +// Validate validates this subnet register request +func (m *SubnetRegisterRequest) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateAccountID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateToken(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *SubnetRegisterRequest) validateAccountID(formats strfmt.Registry) error { + + if err := validate.Required("account_id", "body", m.AccountID); err != nil { + return err + } + + return nil +} + +func (m *SubnetRegisterRequest) validateToken(formats strfmt.Registry) error { + + if err := validate.Required("token", "body", m.Token); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this subnet register request based on context it is used +func (m *SubnetRegisterRequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *SubnetRegisterRequest) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *SubnetRegisterRequest) UnmarshalBinary(b []byte) error { + var res SubnetRegisterRequest + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/operatorapi/configure_operator.go b/operatorapi/configure_operator.go index 365aeb954..490d7fedc 100644 --- a/operatorapi/configure_operator.go +++ b/operatorapi/configure_operator.go @@ -94,8 +94,6 @@ func configureAPI(api *operations.OperatorAPI) http.Handler { registerVolumesHandlers(api) // Namespaces handlers registerNamespaceHandlers(api) - // Subscription handlers - registerSubscriptionHandlers(api) api.PreServerShutdown = func() {} diff --git a/operatorapi/operator_subscription.go b/operatorapi/operator_subscription.go deleted file mode 100644 index e331462ef..000000000 --- a/operatorapi/operator_subscription.go +++ /dev/null @@ -1,416 +0,0 @@ -// This file is part of MinIO Console Server -// Copyright (c) 2021 MinIO, Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . -// - -package operatorapi - -import ( - "context" - "errors" - "time" - - "github.com/minio/console/pkg/subnet" - miniov2 "github.com/minio/operator/pkg/apis/minio.min.io/v2" - corev1 "k8s.io/api/core/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/minio/console/restapi" - - "github.com/go-openapi/runtime/middleware" - "github.com/minio/console/cluster" - "github.com/minio/console/models" - "github.com/minio/console/operatorapi/operations" - "github.com/minio/console/operatorapi/operations/operator_api" -) - -func registerSubscriptionHandlers(api *operations.OperatorAPI) { - // Activate license subscription for a particular tenant - api.OperatorAPISubscriptionActivateHandler = operator_api.SubscriptionActivateHandlerFunc(func(params operator_api.SubscriptionActivateParams, session *models.Principal) middleware.Responder { - err := getSubscriptionActivateResponse(session, params.Namespace, params.Tenant) - if err != nil { - return operator_api.NewSubscriptionActivateDefault(int(err.Code)).WithPayload(err) - } - return operator_api.NewSubscriptionActivateNoContent() - }) - // Refresh license for k8s cluster - api.OperatorAPISubscriptionRefreshHandler = operator_api.SubscriptionRefreshHandlerFunc(func(params operator_api.SubscriptionRefreshParams, session *models.Principal) middleware.Responder { - license, err := getSubscriptionRefreshResponse(session) - if err != nil { - return operator_api.NewSubscriptionRefreshDefault(int(err.Code)).WithPayload(err) - } - return operator_api.NewSubscriptionRefreshOK().WithPayload(license) - }) - // Validate subscription handler - api.OperatorAPISubscriptionValidateHandler = operator_api.SubscriptionValidateHandlerFunc(func(params operator_api.SubscriptionValidateParams, session *models.Principal) middleware.Responder { - license, err := getSubscriptionValidateResponse(session, params.Body) - if err != nil { - return operator_api.NewSubscriptionValidateDefault(int(err.Code)).WithPayload(err) - } - return operator_api.NewSubscriptionValidateOK().WithPayload(license) - }) - // Get subscription information handler - api.OperatorAPISubscriptionInfoHandler = operator_api.SubscriptionInfoHandlerFunc(func(params operator_api.SubscriptionInfoParams, session *models.Principal) middleware.Responder { - license, err := getSubscriptionInfoResponse(session) - if err != nil { - return operator_api.NewSubscriptionInfoDefault(int(err.Code)).WithPayload(err) - } - return operator_api.NewSubscriptionInfoOK().WithPayload(license) - }) - // Refresh license for k8s cluster - api.OperatorAPISubscriptionRefreshHandler = operator_api.SubscriptionRefreshHandlerFunc(func(params operator_api.SubscriptionRefreshParams, session *models.Principal) middleware.Responder { - license, err := getSubscriptionRefreshResponse(session) - if err != nil { - return operator_api.NewSubscriptionRefreshDefault(int(err.Code)).WithPayload(err) - } - return operator_api.NewSubscriptionRefreshOK().WithPayload(license) - }) -} - -// retrieveLicense returns license from K8S secrets -func retrieveLicense(ctx context.Context, sessionToken string) (string, error) { - var license string - - // configure kubernetes client - clientSet, err := cluster.K8sClient(sessionToken) - if err != nil { - return "", err - } - k8sClient := k8sClient{ - client: clientSet, - } - // Get cluster subscription license - license, err = getSubscriptionLicense(ctx, &k8sClient, cluster.Namespace, OperatorSubnetLicenseSecretName) - if err != nil { - return "", err - } - - return license, nil -} - -// getSubscriptionLicense will retrieve stored license jwt from k8s secret -func getSubscriptionLicense(ctx context.Context, clientSet K8sClientI, namespace, secretName string) (string, error) { - // retrieve license stored in k8s - licenseSecret, err := clientSet.getSecret(ctx, namespace, secretName, metav1.GetOptions{}) - if err != nil { - return "", err - } - license, ok := licenseSecret.Data[ConsoleSubnetLicense] - if !ok { - LogError("subnet secret does not contain a valid subnet license") - return "", restapi.ErrorGeneric - } - return string(license), nil -} - -// addSubscriptionLicenseToTenant replace existing console tenant secret and adds the subnet license key -func addSubscriptionLicenseToTenant(ctx context.Context, clientSet K8sClientI, opClient OperatorClientI, license string, tenant *miniov2.Tenant) error { - // If Tenant has a configuration secret update the license there and MinIO pods doesn't need to get restarted - if tenant.HasConfigurationSecret() { - // Update the Tenant Configuration - tenantConfigurationSecret, err := clientSet.getSecret(ctx, tenant.Namespace, tenant.Spec.Configuration.Name, metav1.GetOptions{}) - if err != nil { - return err - } - if _, ok := tenantConfigurationSecret.Data["config.env"]; ok { - updatedTenantConfiguration := map[string]string{} - tenantConfigurationMap := miniov2.ParseRawConfiguration(tenantConfigurationSecret.Data["config.env"]) - for key, val := range tenantConfigurationMap { - updatedTenantConfiguration[key] = string(val) - } - updatedTenantConfiguration[MinIOSubnetLicense] = license - // removing accesskey & secretkey that are added automatically by parsing function - // and are not need it for the tenant itself - delete(updatedTenantConfiguration, "accesskey") - delete(updatedTenantConfiguration, "secretkey") - tenantConfigurationSecret.Data = map[string][]byte{ - "config.env": []byte(GenerateTenantConfigurationFile(updatedTenantConfiguration)), - } - _, err = clientSet.updateSecret(ctx, tenant.Namespace, tenantConfigurationSecret, metav1.UpdateOptions{}) - if err != nil { - return err - } - } else { - return errors.New("tenant configuration secret has wrong format") - } - } else { - // If configuration file is not present set the license to the container env - updatedTenant := tenant.DeepCopy() - // reset container env vars - updatedTenant.Spec.Env = []corev1.EnvVar{} - var licenseIsSet bool - for _, env := range tenant.GetEnvVars() { - // check if license already exists and override - if env.Name == MinIOSubnetLicense { - updatedTenant.Spec.Env = append(updatedTenant.Spec.Env, corev1.EnvVar{ - Name: MinIOSubnetLicense, - Value: license, - }) - licenseIsSet = true - } else { - // copy existing container env variables - updatedTenant.Spec.Env = append(updatedTenant.Spec.Env, env) - } - } - // if license didnt exists append it - if !licenseIsSet { - updatedTenant.Spec.Env = append(updatedTenant.Spec.Env, corev1.EnvVar{ - Name: MinIOSubnetLicense, - Value: license, - }) - } - // this will start MinIO pods rolling restart - _, err := opClient.TenantUpdate(ctx, updatedTenant, metav1.UpdateOptions{}) - if err != nil { - return err - } - } - return nil -} - -func getSubscriptionRefreshResponse(session *models.Principal) (*models.License, *models.Error) { - // 20 seconds timeout - ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) - defer cancel() - client := &cluster.HTTPClient{ - Client: restapi.GetConsoleHTTPClient(), - } - licenseKey, err := retrieveLicense(ctx, session.STSSessionToken) - if err != nil { - return nil, prepareError(errLicenseNotFound, nil, err) - } - newLicenseInfo, licenseRaw, err := subscriptionRefresh(client, licenseKey) - if err != nil { - return nil, prepareError(errLicenseNotFound, nil, err) - } - // configure kubernetes client - clientSet, err := cluster.K8sClient(session.STSSessionToken) - if err != nil { - return nil, prepareError(errLicenseNotFound, nil, err) - } - k8sClient := k8sClient{ - client: clientSet, - } - // save license key to k8s and restart all console pods - if err = saveSubscriptionLicense(ctx, &k8sClient, licenseRaw); err != nil { - return nil, prepareError(restapi.ErrorGeneric, nil, err) - } - // update license for all existing tenants - opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken) - if err != nil { - return nil, prepareError(err) - } - opClient := operatorClient{ - client: opClientClientSet, - } - // iterate over all tenants and update licenses - tenants, err := opClient.TenantList(ctx, "", metav1.ListOptions{}) - if err != nil { - return nil, prepareError(err) - } - for _, tenant := range tenants.Items { - if err = addSubscriptionLicenseToTenant(ctx, &k8sClient, &opClient, licenseRaw, &tenant); err != nil { - return nil, prepareError(err) - } - } - - return newLicenseInfo, nil -} - -// RefreshLicense will check current subnet license and try to renew it -func RefreshLicense() error { - // Get current license - saK8SToken := getK8sSAToken() - licenseKey, err := retrieveLicense(context.Background(), saK8SToken) - if licenseKey == "" { - return errors.New("no license present") - } - if err != nil { - return err - } - client := &cluster.HTTPClient{ - Client: restapi.GetConsoleHTTPClient(), - } - // Attempt to refresh license - _, refreshedLicenseKey, err := subscriptionRefresh(client, licenseKey) - if err != nil { - return err - } - if refreshedLicenseKey == "" { - return errors.New("license expired, please open a support ticket at https://subnet.min.io/") - } - // store new license in memory for console ui - LicenseKey = refreshedLicenseKey - ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) - defer cancel() - clientSet, err := cluster.K8sClient(saK8SToken) - if err != nil { - return err - } - k8sClient := k8sClient{ - client: clientSet, - } - return saveSubscriptionLicense(ctx, &k8sClient, refreshedLicenseKey) -} - -func subscriptionRefresh(httpClient *cluster.HTTPClient, license string) (*models.License, string, error) { - licenseInfo, rawLicense, err := subnet.RefreshLicense(httpClient, license) - if err != nil { - return nil, "", err - } - return &models.License{ - Email: licenseInfo.Email, - AccountID: licenseInfo.AccountID, - StorageCapacity: licenseInfo.StorageCapacity, - Plan: licenseInfo.Plan, - ExpiresAt: licenseInfo.ExpiresAt.String(), - Organization: licenseInfo.Organization, - }, rawLicense, nil -} - -// saveSubscriptionLicense will create or replace an existing subnet license secret in the k8s cluster -func saveSubscriptionLicense(ctx context.Context, clientSet K8sClientI, license string) error { - licenseSecret, err := clientSet.getSecret(ctx, cluster.Namespace, OperatorSubnetLicenseSecretName, metav1.GetOptions{}) - if err != nil { - if k8serrors.IsNotFound(err) { - // Save subnet license in k8s secrets - licenseSecret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: OperatorSubnetLicenseSecretName, - }, - Data: map[string][]byte{ - ConsoleSubnetLicense: []byte(license), - }, - } - _, err = clientSet.createSecret(ctx, cluster.Namespace, licenseSecret, metav1.CreateOptions{}) - if err != nil { - return err - } - return nil - } - return err - } - // update existing license - licenseSecret.Data = map[string][]byte{ - ConsoleSubnetLicense: []byte(license), - } - _, err = clientSet.updateSecret(ctx, cluster.Namespace, licenseSecret, metav1.UpdateOptions{}) - if err != nil { - return err - } - return nil -} - -// subscriptionValidate will validate the provided jwt license against the subnet public key -func subscriptionValidate(client cluster.HTTPClientI, license, email, password string) (*models.License, string, error) { - licenseInfo, rawLicense, err := subnet.ValidateLicense(client, license, email, password) - if err != nil { - return nil, "", err - } - return &models.License{ - Email: licenseInfo.Email, - AccountID: licenseInfo.AccountID, - StorageCapacity: licenseInfo.StorageCapacity, - Plan: licenseInfo.Plan, - ExpiresAt: licenseInfo.ExpiresAt.String(), - Organization: licenseInfo.Organization, - }, rawLicense, nil -} - -// getSubscriptionValidateResponse -func getSubscriptionValidateResponse(session *models.Principal, params *models.SubscriptionValidateRequest) (*models.License, *models.Error) { - // 20 seconds timeout - ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) - defer cancel() - - client := &cluster.HTTPClient{ - Client: restapi.GetConsoleHTTPClient(), - } - // validate license key - licenseInfo, license, err := subscriptionValidate(client, params.License, params.Email, params.Password) - if err != nil { - return nil, prepareError(errInvalidLicense, nil, err) - } - // configure kubernetes client - clientSet, err := cluster.K8sClient(session.STSSessionToken) - k8sClient := k8sClient{ - client: clientSet, - } - if err != nil { - return nil, prepareError(errorGeneric, nil, err) - } - // save license key to k8s - if err = saveSubscriptionLicense(ctx, &k8sClient, license); err != nil { - return nil, prepareError(errorGeneric, nil, err) - } - return licenseInfo, nil -} - -func getSubscriptionActivateResponse(session *models.Principal, namespace, tenantName string) *models.Error { - // 20 seconds timeout - ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) - defer cancel() - opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken) - if err != nil { - return prepareError(errorGeneric, nil, err) - } - clientSet, err := cluster.K8sClient(session.STSSessionToken) - if err != nil { - return prepareError(errorGeneric, nil, err) - } - opClient := operatorClient{ - client: opClientClientSet, - } - tenant, err := getTenant(ctx, &opClient, namespace, tenantName) - if err != nil { - return prepareError(err, errorGeneric) - } - // configure kubernetes client - k8sClient := k8sClient{ - client: clientSet, - } - // Get cluster subscription license - license, err := getSubscriptionLicense(ctx, &k8sClient, cluster.Namespace, OperatorSubnetLicenseSecretName) - if err != nil { - return prepareError(errInvalidCredentials, nil, err) - } - // add subscription license to existing console Tenant - if err = addSubscriptionLicenseToTenant(ctx, &k8sClient, &opClient, license, tenant); err != nil { - return prepareError(err, errorGeneric) - } - return nil -} - -// getSubscriptionInfoResponse returns information about the current configured subnet license for Console -func getSubscriptionInfoResponse(session *models.Principal) (*models.License, *models.Error) { - // 20 seconds timeout - ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) - defer cancel() - var licenseInfo *models.License - client := &cluster.HTTPClient{ - Client: restapi.GetConsoleHTTPClient(), - } - licenseKey, err := retrieveLicense(ctx, session.STSSessionToken) - if err != nil { - return nil, prepareError(errLicenseNotFound, nil, err) - } - // validate license key and obtain license info - licenseInfo, _, err = subscriptionValidate(client, licenseKey, "", "") - if err != nil { - return nil, prepareError(errLicenseNotFound, nil, err) - } - return licenseInfo, nil -} diff --git a/operatorapi/operator_subscription_test.go b/operatorapi/operator_subscription_test.go deleted file mode 100644 index 082d76831..000000000 --- a/operatorapi/operator_subscription_test.go +++ /dev/null @@ -1,359 +0,0 @@ -// This file is part of MinIO Kubernetes Cloud -// Copyright (c) 2021 MinIO, Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -package operatorapi - -import ( - "context" - "testing" - - v2 "github.com/minio/operator/pkg/apis/minio.min.io/v2" - - "errors" - - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func Test_addSubscriptionLicenseToTenant(t *testing.T) { - k8sClient := k8sClientMock{} - opClient := opClientMock{} - tenant := &v2.Tenant{ - ObjectMeta: metav1.ObjectMeta{}, - Spec: v2.TenantSpec{}, - } - type args struct { - ctx context.Context - clientSet K8sClientI - opClient OperatorClientI - license string - tenant *v2.Tenant - } - tests := []struct { - name string - args args - wantErr bool - mockFunc func() - }{ - { - name: "success updating subscription for tenant with configuration file", - args: args{ - ctx: context.Background(), - clientSet: k8sClient, - opClient: opClient, - license: "", - tenant: tenant, - }, - wantErr: false, - mockFunc: func() { - tenant.Spec.Configuration = &corev1.LocalObjectReference{ - Name: "minio-configuration", - } - k8sclientGetSecretMock = func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) { - return &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "minio-configuration", - }, - Data: map[string][]byte{ - "config.env": []byte("export MINIO_SUBNET_LICENSE=\"eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJsZW5pbitjMUBtaW5pby5pbyIsInRlYW1OYW1lIjoiY29uc29sZS1jdXN0b21lciIsImV4cCI6MS42Mzk5NTI2MTE2MDkxNDQ3MzJlOSwiaXNzIjoic3VibmV0QG1pbmlvLmlvIiwiY2FwYWNpdHkiOjI1LCJpYXQiOjEuNjA4NDE2NjExNjA5MTQ0NzMyZTksImFjY291bnRJZCI6MTc2LCJzZXJ2aWNlVHlwZSI6IlNUQU5EQVJEIn0.ndtf8V_FJTvhXeemVLlORyDev6RJaSPhZ2djkMVK9SvXD0srR_qlYJATPjC4NljkS71nXMGVDov5uCTuUL97x6FGQEKDruA-z24x_2Zr8kof4LfBb3HUHudCR8QvE--I\""), - }, - }, nil - } - UpdateSecretMock = func(ctx context.Context, namespace string, secret *corev1.Secret, opts metav1.UpdateOptions) (*corev1.Secret, error) { - return nil, nil - } - }, - }, - { - name: "error updating subscription for tenant because cannot get configuration file", - args: args{ - ctx: context.Background(), - clientSet: k8sClient, - opClient: opClient, - license: "", - tenant: tenant, - }, - wantErr: true, - mockFunc: func() { - tenant.Spec.Configuration = &corev1.LocalObjectReference{ - Name: "minio-configuration", - } - k8sclientGetSecretMock = func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) { - return nil, errors.New("something wrong happened") - } - UpdateSecretMock = func(ctx context.Context, namespace string, secret *corev1.Secret, opts metav1.UpdateOptions) (*corev1.Secret, error) { - return nil, nil - } - }, - }, - { - name: "error updating subscription for tenant because configuration file has wrong format", - args: args{ - ctx: context.Background(), - clientSet: k8sClient, - opClient: opClient, - license: "", - tenant: tenant, - }, - wantErr: true, - mockFunc: func() { - tenant.Spec.Configuration = &corev1.LocalObjectReference{ - Name: "minio-configuration", - } - k8sclientGetSecretMock = func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) { - return &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "minio-configuration", - }, - Data: map[string][]byte{ - "aaaaa": []byte("export MINIO_SUBNET_LICENSE=\"eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJsZW5pbitjMUBtaW5pby5pbyIsInRlYW1OYW1lIjoiY29uc29sZS1jdXN0b21lciIsImV4cCI6MS42Mzk5NTI2MTE2MDkxNDQ3MzJlOSwiaXNzIjoic3VibmV0QG1pbmlvLmlvIiwiY2FwYWNpdHkiOjI1LCJpYXQiOjEuNjA4NDE2NjExNjA5MTQ0NzMyZTksImFjY291bnRJZCI6MTc2LCJzZXJ2aWNlVHlwZSI6IlNUQU5EQVJEIn0.ndtf8V_FJTvhXeemVLlORyDev6RJaSPhZ2djkMVK9SvXD0srR_qlYJATPjC4NljkS71nXMGVDov5uCTuUL97x6FGQEKDruA-z24x_2Zr8kof4LfBb3HUHudCR8QvE--I\""), - }, - }, nil - } - UpdateSecretMock = func(ctx context.Context, namespace string, secret *corev1.Secret, opts metav1.UpdateOptions) (*corev1.Secret, error) { - return nil, nil - } - }, - }, - { - name: "error updating subscription for tenant because cannot update configuration file", - args: args{ - ctx: context.Background(), - clientSet: k8sClient, - opClient: opClient, - license: "", - tenant: tenant, - }, - wantErr: true, - mockFunc: func() { - tenant.Spec.Configuration = &corev1.LocalObjectReference{ - Name: "minio-configuration", - } - k8sclientGetSecretMock = func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) { - return &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "minio-configuration", - }, - Data: map[string][]byte{ - "config.env": []byte("export MINIO_SUBNET_LICENSE=\"eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJsZW5pbitjMUBtaW5pby5pbyIsInRlYW1OYW1lIjoiY29uc29sZS1jdXN0b21lciIsImV4cCI6MS42Mzk5NTI2MTE2MDkxNDQ3MzJlOSwiaXNzIjoic3VibmV0QG1pbmlvLmlvIiwiY2FwYWNpdHkiOjI1LCJpYXQiOjEuNjA4NDE2NjExNjA5MTQ0NzMyZTksImFjY291bnRJZCI6MTc2LCJzZXJ2aWNlVHlwZSI6IlNUQU5EQVJEIn0.ndtf8V_FJTvhXeemVLlORyDev6RJaSPhZ2djkMVK9SvXD0srR_qlYJATPjC4NljkS71nXMGVDov5uCTuUL97x6FGQEKDruA-z24x_2Zr8kof4LfBb3HUHudCR8QvE--I\""), - }, - }, nil - } - UpdateSecretMock = func(ctx context.Context, namespace string, secret *corev1.Secret, opts metav1.UpdateOptions) (*corev1.Secret, error) { - return nil, errors.New("something wrong happened") - } - - }, - }, - { - name: "success updating subscription for tenant with env variable", - args: args{ - ctx: context.Background(), - clientSet: k8sClient, - opClient: opClient, - license: "", - tenant: tenant, - }, - wantErr: false, - mockFunc: func() { - tenant.Spec.Env = []corev1.EnvVar{ - { - Name: "MINIO_SUBNET_LICENSE", - Value: "", - ValueFrom: nil, - }, - } - opClientTenantUpdateMock = func(ctx context.Context, tenant *v2.Tenant, opts metav1.UpdateOptions) (*v2.Tenant, error) { - return nil, nil - } - }, - }, - { - name: "error updating subscription for tenant with env variable because of update tenant error", - args: args{ - ctx: context.Background(), - clientSet: k8sClient, - opClient: opClient, - license: "", - tenant: tenant, - }, - wantErr: true, - mockFunc: func() { - tenant.Spec.Env = []corev1.EnvVar{ - { - Name: "MINIO_SUBNET_LICENSE", - Value: "", - ValueFrom: nil, - }, - } - opClientTenantUpdateMock = func(ctx context.Context, tenant *v2.Tenant, opts metav1.UpdateOptions) (*v2.Tenant, error) { - return nil, errors.New("something wrong happened") - } - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.mockFunc != nil { - tt.mockFunc() - } - if err := addSubscriptionLicenseToTenant(tt.args.ctx, tt.args.clientSet, tt.args.opClient, tt.args.license, tt.args.tenant); (err != nil) != tt.wantErr { - t.Errorf("addSubscriptionLicenseToTenant() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func Test_saveSubscriptionLicense(t *testing.T) { - k8sClient := k8sClientMock{} - type args struct { - ctx context.Context - clientSet K8sClientI - license string - } - tests := []struct { - name string - args args - wantErr bool - mockFunc func() - }{ - { - name: "error deleting existing secret", - args: args{ - ctx: context.Background(), - clientSet: k8sClient, - license: "1111111111", - }, - mockFunc: func() { - DeleteSecretMock = func(ctx context.Context, namespace string, name string, opts metav1.DeleteOptions) error { - return nil - } - CreateSecretMock = func(ctx context.Context, namespace string, secret *corev1.Secret, opts metav1.CreateOptions) (*corev1.Secret, error) { - return nil, errors.New("something went wrong") - } - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.mockFunc != nil { - tt.mockFunc() - } - if err := saveSubscriptionLicense(tt.args.ctx, tt.args.clientSet, tt.args.license); (err != nil) != tt.wantErr { - t.Errorf("saveSubscriptionLicense() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func Test_getSubscriptionLicense(t *testing.T) { - license := "eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJsZW5pbitjMUBtaW5pby5pbyIsInRlYW1OYW1lIjoiY29uc29sZS1jdXN0b21lciIsImV4cCI6MS42Mzk5NTI2MTE2MDkxNDQ3MzJlOSwiaXNzIjoic3VibmV0QG1pbmlvLmlvIiwiY2FwYWNpdHkiOjI1LCJpYXQiOjEuNjA4NDE2NjExNjA5MTQ0NzMyZTksImFjY291bnRJZCI6MTc2LCJzZXJ2aWNlVHlwZSI6IlNUQU5EQVJEIn0.ndtf8V_FJTvhXeemVLlORyDev6RJaSPhZ2djkMVK9SvXD0srR_qlYJATPjC4NljkS71nXMGVDov5uCTuUL97x6FGQEKDruA-z24x_2Zr8kof4LfBb3HUHudCR8QvE--I" - k8sClient := k8sClientMock{} - type args struct { - ctx context.Context - clientSet K8sClientI - namespace string - secretName string - } - tests := []struct { - name string - args args - want string - wantErr bool - mockFunc func() - }{ - { - name: "error because subscription license doesnt exists", - args: args{ - ctx: context.Background(), - clientSet: k8sClient, - namespace: "namespace", - secretName: OperatorSubnetLicenseSecretName, - }, - wantErr: true, - mockFunc: func() { - k8sclientGetSecretMock = func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) { - return nil, errors.New("something went wrong") - } - }, - }, - { - name: "error because license field doesnt exist in k8s secret", - args: args{ - ctx: context.Background(), - clientSet: k8sClient, - namespace: "namespace", - secretName: OperatorSubnetLicenseSecretName, - }, - wantErr: true, - mockFunc: func() { - k8sclientGetSecretMock = func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) { - imm := true - return &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: OperatorSubnetLicenseSecretName, - }, - Immutable: &imm, - Data: map[string][]byte{ - //ConsoleSubnetLicense: []byte(license), - }, - }, nil - } - }, - }, - { - name: "license obtained successfully", - args: args{ - ctx: context.Background(), - clientSet: k8sClient, - namespace: "namespace", - secretName: OperatorSubnetLicenseSecretName, - }, - wantErr: false, - want: license, - mockFunc: func() { - k8sclientGetSecretMock = func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) { - imm := true - return &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: OperatorSubnetLicenseSecretName, - }, - Immutable: &imm, - Data: map[string][]byte{ - ConsoleSubnetLicense: []byte(license), - }, - }, nil - } - }, - }, - } - for _, tt := range tests { - if tt.mockFunc != nil { - tt.mockFunc() - } - t.Run(tt.name, func(t *testing.T) { - got, err := getSubscriptionLicense(tt.args.ctx, tt.args.clientSet, tt.args.namespace, tt.args.secretName) - if (err != nil) != tt.wantErr { - t.Errorf("getSubscriptionLicense() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("getSubscriptionLicense() got = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/operatorapi/operator_tenants.go b/operatorapi/operator_tenants.go index 02882aff8..6854711bf 100644 --- a/operatorapi/operator_tenants.go +++ b/operatorapi/operator_tenants.go @@ -558,18 +558,6 @@ func getTenantDetailsResponse(session *models.Principal, params operator_api.Ten info.IdpOidcEnabled = oidcEnabled info.MinioTLS = minTenant.TLS() - // obtain current subnet license for tenant (if exists) - if license, ok := tenantConfiguration[MinIOSubnetLicense]; ok { - client := &cluster.HTTPClient{ - Client: restapi.GetConsoleHTTPClient(), - } - licenseInfo, _, _ := subscriptionValidate(client, string(license), "", "") - // if licenseInfo is present attach it to the tenantInfo response - if licenseInfo != nil { - info.SubnetLicense = licenseInfo - } - } - // attach status information info.Status = &models.TenantStatus{ HealthStatus: string(minTenant.Status.HealthStatus), @@ -1241,13 +1229,6 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre } } - // If Subnet License is present in k8s secrets, copy that to the MINIO_SUBNET_LICENSE env variable - // of the console tenant - license, _ := getSubscriptionLicense(ctx, &k8sClient, cluster.Namespace, OperatorSubnetLicenseSecretName) - if license != "" { - tenantConfigurationENV[MinIOSubnetLicense] = license - } - // add annotations var annotations map[string]string diff --git a/pkg/subnet/config.go b/pkg/subnet/config.go index 00f4c29f6..1f06b9625 100644 --- a/pkg/subnet/config.go +++ b/pkg/subnet/config.go @@ -49,3 +49,15 @@ func GetLicenseInfoFromJWT(license string, publicKeys []string) (*licverifier.Li } return nil, errors.New("invalid license key") } + +// MfaReq - JSON payload of the SUBNET mfa api +type MfaReq struct { + Username string `json:"username"` + OTP string `json:"otp"` + Token string `json:"token"` +} + +type LoginResp struct { + AccessToken string + MfaToken string +} diff --git a/pkg/subnet/const.go b/pkg/subnet/const.go index 5b7706097..aabe6953c 100644 --- a/pkg/subnet/const.go +++ b/pkg/subnet/const.go @@ -29,9 +29,4 @@ JkO2PfyyAYEO/5dBlPh1Undu9WQl6J7B const ( // Constants for subnet configuration ConsoleSubnetURL = "CONSOLE_SUBNET_URL" - // Subnet endpoints - publicKey = "/downloads/license-pubkey.pem" - loginEndpoint = "/api/auth/login" - refreshLicenseKeyEndpoint = "/api/auth/subscription/renew-license" - licenseKeyEndpoint = "/api/auth/subscription/license-key" ) diff --git a/pkg/subnet/subnet.go b/pkg/subnet/subnet.go index 3b0d1b1c0..88f4a0469 100644 --- a/pkg/subnet/subnet.go +++ b/pkg/subnet/subnet.go @@ -18,209 +18,94 @@ package subnet import ( - "bytes" "encoding/json" "errors" - "fmt" - "io/ioutil" "log" - "net/http" + + "github.com/minio/console/models" + "github.com/minio/madmin-go" + mc "github.com/minio/mc/cmd" + "github.com/tidwall/gjson" "github.com/minio/console/cluster" - "github.com/minio/pkg/licverifier" ) -// subnetLoginRequest body request for subnet login -type subnetLoginRequest struct { - Username string `json:"username"` - Password string `json:"password"` +func LoginWithMFA(client cluster.HTTPClientI, username, mfaToken, otp string) (*LoginResp, error) { + mfaLoginReq := MfaReq{Username: username, OTP: otp, Token: mfaToken} + resp, err := subnetPostReq(client, subnetMFAURL(), mfaLoginReq, nil) + if err != nil { + return nil, err + } + token := gjson.Get(resp, "token_info.access_token") + if token.Exists() { + return &LoginResp{AccessToken: token.String(), MfaToken: ""}, nil + } + return nil, errors.New("access token not found in response") } -// tokenInfo -type tokenInfo struct { - AccessToken string `json:"access_token"` - ExpiresIn float64 `json:"expires_in"` - TokenType string `json:"token_type"` -} - -// subnetLoginResponse body resonse from subnet after login -type subnetLoginResponse struct { - HasMembership bool `json:"has_memberships"` - TokenInfo tokenInfo `json:"token_info"` -} - -// LicenseMetadata claims in subnet license -type LicenseMetadata struct { - Email string `json:"email"` - Issuer string `json:"issuer"` - TeamName string `json:"teamName"` - ServiceType string `json:"serviceType"` - RequestedAt string `json:"requestedAt"` - ExpiresAt string `json:"expiresAt"` - AccountID int64 `json:"accountId"` - Capacity int64 `json:"capacity"` -} - -// subnetLicenseResponse body response returned by subnet license endpoint -type subnetLicenseResponse struct { - License string `json:"license"` - Metadata LicenseMetadata `json:"metadata"` -} - -// subnetLoginRequest body request for subnet login -type subnetRefreshRequest struct { - License string `json:"license"` -} - -// getNewLicenseFromExistingLicense will perform license refresh based on the provided license key -func getNewLicenseFromExistingLicense(client cluster.HTTPClientI, licenseKey string) (string, error) { - request := subnetRefreshRequest{ - License: licenseKey, +func Login(client cluster.HTTPClientI, username, password string) (*LoginResp, error) { + loginReq := map[string]string{ + "username": username, + "password": password, } - // http body for login request - payloadBytes, err := json.Marshal(request) + respStr, err := subnetPostReq(client, subnetLoginURL(), loginReq, nil) if err != nil { - return "", err + return nil, err } - subnetURL := GetSubnetURL() - url := fmt.Sprintf("%s%s", subnetURL, refreshLicenseKeyEndpoint) - resp, err := client.Post(url, "application/json", bytes.NewReader(payloadBytes)) - if err != nil { - return "", err - } - defer resp.Body.Close() - bodyBytes, err := ioutil.ReadAll(resp.Body) - if err != nil { - return "", err - } - subnetLicense := &subnetLicenseResponse{} - // Parse subnet login response - err = json.Unmarshal(bodyBytes, subnetLicense) - if err != nil { - return "", err - } - return subnetLicense.License, nil -} - -// getLicenseFromCredentials will perform authentication against subnet using -// user provided credentials and return the current subnet license key -func getLicenseFromCredentials(client cluster.HTTPClientI, username, password string) (string, error) { - request := subnetLoginRequest{ - Username: username, - Password: password, - } - // http body for login request - payloadBytes, err := json.Marshal(request) - if err != nil { - return "", err - } - subnetURL := GetSubnetURL() - url := fmt.Sprintf("%s%s", subnetURL, loginEndpoint) - // Authenticate against subnet using email/password provided by user - resp, err := client.Post(url, "application/json", bytes.NewReader(payloadBytes)) - if err != nil { - return "", err - } - defer resp.Body.Close() - bodyBytes, err := ioutil.ReadAll(resp.Body) - if err != nil { - return "", err - } - subnetSession := &subnetLoginResponse{} - // Parse subnet login response - err = json.Unmarshal(bodyBytes, subnetSession) - if err != nil { - return "", err - } - - // Get license key using session token - token := subnetSession.TokenInfo.AccessToken - url = fmt.Sprintf("%s%s", subnetURL, licenseKeyEndpoint) - req, err := http.NewRequest("POST", url, nil) - if err != nil { - return "", err - } - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token)) - resp, err = client.Do(req) - if err != nil { - return "", err - } - defer resp.Body.Close() - bodyBytes, err = ioutil.ReadAll(resp.Body) - if err != nil { - return "", err - } - if resp.StatusCode < 200 || resp.StatusCode >= 300 { - return "", fmt.Errorf("subnet served returned status %d code", resp.StatusCode) - } - userLicense := &subnetLicenseResponse{} - // Parse subnet license response - err = json.Unmarshal(bodyBytes, userLicense) - if err != nil { - return "", err - } - return userLicense.License, nil -} - -// downloadSubnetPublicKey will download the current subnet public key. -func downloadSubnetPublicKey(client cluster.HTTPClientI) (string, error) { - // Get the public key directly from Subnet - url := fmt.Sprintf("%s%s", GetSubnetURL(), publicKey) - resp, err := client.Get(url) - if err != nil { - return "", err - } - defer resp.Body.Close() - buf := new(bytes.Buffer) - _, err = buf.ReadFrom(resp.Body) - if err != nil { - return "", err - } - return buf.String(), err -} - -// ValidateLicense will download the current subnet public key, if the public key its not available for license -// verification then console will fall back to verification with hardcoded public keys -func ValidateLicense(client cluster.HTTPClientI, licenseKey, email, password string) (licInfo *licverifier.LicenseInfo, license string, err error) { - var publicKeys []string - if email != "" && password != "" { - // fetch subnet license key using user credentials - license, err = getLicenseFromCredentials(client, email, password) - if err != nil { - return nil, "", err + mfaRequired := gjson.Get(respStr, "mfa_required").Bool() + if mfaRequired { + mfaToken := gjson.Get(respStr, "mfa_token").String() + if mfaToken == "" { + return nil, errors.New("missing mfa token") } - } else if licenseKey != "" { - license = licenseKey + return &LoginResp{AccessToken: "", MfaToken: mfaToken}, nil + } + token := gjson.Get(respStr, "token_info.access_token") + if token.Exists() { + return &LoginResp{AccessToken: token.String(), MfaToken: ""}, nil + } + return nil, errors.New("access token not found in response") +} + +func GetOrganizations(client cluster.HTTPClientI, token string) ([]*models.SubnetOrganization, error) { + headers := subnetAuthHeaders(token) + respStr, err := subnetGetReq(client, subnetOrgsURL(), headers) + if err != nil { + return nil, err + } + var organizations []*models.SubnetOrganization + err = json.Unmarshal([]byte(respStr), &organizations) + if err != nil { + log.Println(err) + } + return organizations, nil +} + +func Register(client cluster.HTTPClientI, admInfo madmin.InfoMessage, apiKey, token, accountID string) (string, error) { + var headers map[string]string + regInfo := GetClusterRegInfo(admInfo) + regURL := subnetRegisterURL() + if apiKey != "" { + regURL += "?api_key=" + apiKey } else { - return nil, "", errors.New("invalid license") - } - subnetPubKey, err := downloadSubnetPublicKey(client) - if err != nil { - log.Print(err) - // there was an issue getting the subnet public key - // use hardcoded public keys instead - publicKeys = OfflinePublicKeys - } else { - publicKeys = append(publicKeys, subnetPubKey) - } - licInfo, err = GetLicenseInfoFromJWT(license, publicKeys) - if err != nil { - return nil, "", err - } - return licInfo, license, nil -} - -func RefreshLicense(client cluster.HTTPClientI, licenseKey string) (licInfo *licverifier.LicenseInfo, license string, err error) { - if licenseKey != "" { - license, err = getNewLicenseFromExistingLicense(client, licenseKey) - if err != nil { - return nil, "", err + if accountID == "" || token == "" { + return "", errors.New("missing accountID or authentication token") } - licenseInfo, rawLicense, err := ValidateLicense(client, license, "", "") - if err != nil { - return nil, "", err - } - return licenseInfo, rawLicense, nil + headers = subnetAuthHeaders(token) + regURL += "?aid=" + accountID } - return nil, "", errors.New("invalid license") + regToken, err := GenerateRegToken(regInfo) + if err != nil { + return "", err + } + reqPayload := mc.ClusterRegistrationReq{Token: regToken} + resp, err := subnetPostReq(client, regURL, reqPayload, headers) + if err != nil { + return "", err + } + subnetAPIKey := gjson.Parse(resp).Get("api_key").String() + if subnetAPIKey != "" { + return subnetAPIKey, nil + } + return "", errors.New("subnet api key not found") } diff --git a/pkg/subnet/subnet_test.go b/pkg/subnet/subnet_test.go deleted file mode 100644 index fbe97e15c..000000000 --- a/pkg/subnet/subnet_test.go +++ /dev/null @@ -1,332 +0,0 @@ -// This file is part of MinIO Kubernetes Cloud -// Copyright (c) 2021 MinIO, Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -package subnet - -import ( - "bytes" - "io" - "io/ioutil" - "net/http" - "strings" - "testing" - - "errors" -) - -var HTTPGetMock func(url string) (resp *http.Response, err error) -var HTTPPostMock func(url, contentType string, body io.Reader) (resp *http.Response, err error) -var HTTPDoMock func(req *http.Request) (*http.Response, error) - -type HTTPClientMock struct { - Client *http.Client -} - -func (c *HTTPClientMock) Get(url string) (resp *http.Response, err error) { - return HTTPGetMock(url) -} - -func (c *HTTPClientMock) Post(url, contentType string, body io.Reader) (resp *http.Response, err error) { - return HTTPPostMock(url, contentType, body) -} - -func (c *HTTPClientMock) Do(req *http.Request) (*http.Response, error) { - return HTTPDoMock(req) -} - -func Test_getLicenseFromCredentials(t *testing.T) { - // HTTP Client mock - clientMock := HTTPClientMock{ - Client: &http.Client{}, - } - type args struct { - client HTTPClientMock - username string - password string - } - tests := []struct { - name string - args args - want string - wantErr bool - mockFunc func() - }{ - { - name: "error when login against subnet", - args: args{ - client: clientMock, - username: "invalid", - password: "invalid", - }, - want: "", - wantErr: true, - mockFunc: func() { - HTTPPostMock = func(url, contentType string, body io.Reader) (resp *http.Response, err error) { - return nil, errors.New("something went wrong") - } - }, - }, - { - name: "error because of malformed subnet response", - args: args{ - client: clientMock, - username: "invalid", - password: "invalid", - }, - want: "", - wantErr: true, - mockFunc: func() { - HTTPPostMock = func(url, contentType string, body io.Reader) (resp *http.Response, err error) { - return &http.Response{Body: ioutil.NopCloser(bytes.NewReader([]byte("foo")))}, nil - } - }, - }, - { - name: "error when obtaining license from subnet", - args: args{ - client: clientMock, - username: "valid", - password: "valid", - }, - want: "", - wantErr: true, - mockFunc: func() { - HTTPPostMock = func(url, contentType string, body io.Reader) (resp *http.Response, err error) { - // returning test jwt token - return &http.Response{Body: ioutil.NopCloser(bytes.NewReader([]byte("{\"has_memberships\":true,\"token_info\":{\"access_token\":\"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ik4wRXdOa1V5UXpORU1UUkNOekU0UmpSR1JVWkJSa1UxUmtZNE9EY3lOekZHTXpjNU1qZ3hNZyJ9.eyJodHRwczovL2lkLnN1Ym5ldC5taW4uaW8vY2xhaW1zL2dyb3VwcyI6W10sImh0dHBzOi8vaWQuc3VibmV0Lm1pbi5pby9jbGFpbXMvcm9sZXMiOltdLCJodHRwczovL2lkLnN1Ym5ldC5taW4uaW8vY2xhaW1zL2VtYWlsIjoibGVuaW4rYzFAbWluaW8uaW8iLCJpc3MiOiJodHRwczovL2lkLnN1Ym5ldC5taW4uaW8vIiwic3ViIjoiYXV0aDB8NWZjZWFlYTMyNTNhZjEwMDc3NDZkMDM0IiwiYXVkIjoiaHR0cHM6Ly9zdWJuZXQubWluLmlvL2FwaSIsImlhdCI6MTYwODQxNjE5NiwiZXhwIjoxNjExMDA4MTk2LCJhenAiOiI1WTA0eVZlejNiOFgxUFVzRHVqSmxuZXVuY3ExVjZxaiIsInNjb3BlIjoib2ZmbGluZV9hY2Nlc3MiLCJndHkiOiJwYXNzd29yZCJ9.GC8DRLT0jUEteuBZBmyMXMswLSblCr_89Gu5NcVRUzKSYAaZ5VFW4UFgo1BpiC0sePuWJ0Vykitphx7znTfZfj5B3mZbOw3ejG6kxz7nm9DuYMmySJFYnwroZ9EP02vkW7-n_-YvEg8le1wXfkJ3lTUzO3aWddS4rfQRsZ2YJJUj61GiNyEK_QNP4PrYOuzLyD1wV75NejFqfcFoj7nRkT1K2BM0-89-_f2AFDGTjov6Ig6s1s-zLC9wxcYSmubNwpCJytZmQgPqIepOr065Y6OB4n0n0B5sXguuGuzb8VAkECrHhHPz8ta926fc0jC4XxVCNKdbV1_qC3-1yY7AJA\",\"expires_in\":2592000.0,\"token_type\":\"Bearer\"}}")))}, nil - } - HTTPDoMock = func(req *http.Request) (*http.Response, error) { - return nil, errors.New("something went wrong") - } - }, - }, - { - name: "error when obtaining license from subnet because of malformed response", - args: args{ - client: clientMock, - username: "valid", - password: "valid", - }, - want: "", - wantErr: true, - mockFunc: func() { - HTTPPostMock = func(url, contentType string, body io.Reader) (resp *http.Response, err error) { - // returning test jwt token - return &http.Response{Body: ioutil.NopCloser(bytes.NewReader([]byte("{\"has_memberships\":true,\"token_info\":{\"access_token\":\"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ik4wRXdOa1V5UXpORU1UUkNOekU0UmpSR1JVWkJSa1UxUmtZNE9EY3lOekZHTXpjNU1qZ3hNZyJ9.eyJodHRwczovL2lkLnN1Ym5ldC5taW4uaW8vY2xhaW1zL2dyb3VwcyI6W10sImh0dHBzOi8vaWQuc3VibmV0Lm1pbi5pby9jbGFpbXMvcm9sZXMiOltdLCJodHRwczovL2lkLnN1Ym5ldC5taW4uaW8vY2xhaW1zL2VtYWlsIjoibGVuaW4rYzFAbWluaW8uaW8iLCJpc3MiOiJodHRwczovL2lkLnN1Ym5ldC5taW4uaW8vIiwic3ViIjoiYXV0aDB8NWZjZWFlYTMyNTNhZjEwMDc3NDZkMDM0IiwiYXVkIjoiaHR0cHM6Ly9zdWJuZXQubWluLmlvL2FwaSIsImlhdCI6MTYwODQxNjE5NiwiZXhwIjoxNjExMDA4MTk2LCJhenAiOiI1WTA0eVZlejNiOFgxUFVzRHVqSmxuZXVuY3ExVjZxaiIsInNjb3BlIjoib2ZmbGluZV9hY2Nlc3MiLCJndHkiOiJwYXNzd29yZCJ9.GC8DRLT0jUEteuBZBmyMXMswLSblCr_89Gu5NcVRUzKSYAaZ5VFW4UFgo1BpiC0sePuWJ0Vykitphx7znTfZfj5B3mZbOw3ejG6kxz7nm9DuYMmySJFYnwroZ9EP02vkW7-n_-YvEg8le1wXfkJ3lTUzO3aWddS4rfQRsZ2YJJUj61GiNyEK_QNP4PrYOuzLyD1wV75NejFqfcFoj7nRkT1K2BM0-89-_f2AFDGTjov6Ig6s1s-zLC9wxcYSmubNwpCJytZmQgPqIepOr065Y6OB4n0n0B5sXguuGuzb8VAkECrHhHPz8ta926fc0jC4XxVCNKdbV1_qC3-1yY7AJA\",\"expires_in\":2592000.0,\"token_type\":\"Bearer\"}}")))}, nil - } - HTTPDoMock = func(req *http.Request) (*http.Response, error) { - return &http.Response{Body: ioutil.NopCloser(bytes.NewReader([]byte("foo")))}, nil - } - }, - }, - { - name: "license obtained successfully", - args: args{ - client: clientMock, - username: "valid", - password: "valid", - }, - want: license, - wantErr: false, - mockFunc: func() { - HTTPPostMock = func(url, contentType string, body io.Reader) (resp *http.Response, err error) { - // returning test jwt token - return &http.Response{ - StatusCode: 200, - Body: ioutil.NopCloser(bytes.NewReader([]byte("{\"has_memberships\":true,\"token_info\":{\"access_token\":\"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ik4wRXdOa1V5UXpORU1UUkNOekU0UmpSR1JVWkJSa1UxUmtZNE9EY3lOekZHTXpjNU1qZ3hNZyJ9.eyJodHRwczovL2lkLnN1Ym5ldC5taW4uaW8vY2xhaW1zL2dyb3VwcyI6W10sImh0dHBzOi8vaWQuc3VibmV0Lm1pbi5pby9jbGFpbXMvcm9sZXMiOltdLCJodHRwczovL2lkLnN1Ym5ldC5taW4uaW8vY2xhaW1zL2VtYWlsIjoibGVuaW4rYzFAbWluaW8uaW8iLCJpc3MiOiJodHRwczovL2lkLnN1Ym5ldC5taW4uaW8vIiwic3ViIjoiYXV0aDB8NWZjZWFlYTMyNTNhZjEwMDc3NDZkMDM0IiwiYXVkIjoiaHR0cHM6Ly9zdWJuZXQubWluLmlvL2FwaSIsImlhdCI6MTYwODQxNjE5NiwiZXhwIjoxNjExMDA4MTk2LCJhenAiOiI1WTA0eVZlejNiOFgxUFVzRHVqSmxuZXVuY3ExVjZxaiIsInNjb3BlIjoib2ZmbGluZV9hY2Nlc3MiLCJndHkiOiJwYXNzd29yZCJ9.GC8DRLT0jUEteuBZBmyMXMswLSblCr_89Gu5NcVRUzKSYAaZ5VFW4UFgo1BpiC0sePuWJ0Vykitphx7znTfZfj5B3mZbOw3ejG6kxz7nm9DuYMmySJFYnwroZ9EP02vkW7-n_-YvEg8le1wXfkJ3lTUzO3aWddS4rfQRsZ2YJJUj61GiNyEK_QNP4PrYOuzLyD1wV75NejFqfcFoj7nRkT1K2BM0-89-_f2AFDGTjov6Ig6s1s-zLC9wxcYSmubNwpCJytZmQgPqIepOr065Y6OB4n0n0B5sXguuGuzb8VAkECrHhHPz8ta926fc0jC4XxVCNKdbV1_qC3-1yY7AJA\",\"expires_in\":2592000.0,\"token_type\":\"Bearer\"}}"))), - }, nil - } - HTTPDoMock = func(req *http.Request) (*http.Response, error) { - // returning test jwt license - return &http.Response{ - StatusCode: 200, - Body: ioutil.NopCloser(bytes.NewReader([]byte("{\"license\":\"" + license + "\",\"metadata\":{\"email\":\"lenin+c1@minio.io\",\"issuer\":\"subnet@minio.io\",\"accountId\":176,\"teamName\":\"console-customer\",\"serviceType\":\"STANDARD\",\"capacity\":25,\"requestedAt\":\"2020-12-19T22:23:31.609144732Z\",\"expiresAt\":\"2021-12-19T22:23:31.609144732Z\"}}"))), - }, nil - } - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.mockFunc != nil { - tt.mockFunc() - } - got, err := getLicenseFromCredentials(&tt.args.client, tt.args.username, tt.args.password) - if (err != nil) != tt.wantErr { - t.Errorf("getLicenseFromCredentials() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("getLicenseFromCredentials() got = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_downloadSubnetPublicKey(t *testing.T) { - // HTTP Client mock - clientMock := HTTPClientMock{ - Client: &http.Client{}, - } - type args struct { - client HTTPClientMock - } - tests := []struct { - name string - args args - want string - wantErr bool - mockFunc func() - }{ - { - name: "error downloading public key", - args: args{ - client: clientMock, - }, - mockFunc: func() { - HTTPGetMock = func(url string) (resp *http.Response, err error) { - return nil, errors.New("something went wrong") - } - }, - wantErr: true, - want: "", - }, - { - name: "public key download successfully", - args: args{ - client: clientMock, - }, - mockFunc: func() { - HTTPGetMock = func(url string) (resp *http.Response, err error) { - return &http.Response{Body: ioutil.NopCloser(bytes.NewReader([]byte("foo")))}, nil - } - }, - wantErr: false, - want: "foo", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.mockFunc != nil { - tt.mockFunc() - } - got, err := downloadSubnetPublicKey(&tt.args.client) - if (err != nil) != tt.wantErr { - t.Errorf("downloadSubnetPublicKey() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("downloadSubnetPublicKey() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestValidateLicense(t *testing.T) { - // HTTP Client mock - clientMock := HTTPClientMock{ - Client: &http.Client{}, - } - type args struct { - client HTTPClientMock - licenseKey string - email string - password string - } - tests := []struct { - name string - args args - wantLicense string - wantErr bool - mockFunc func() - }{ - { - name: "error because nor license nor user or password was provided", - args: args{ - client: clientMock, - licenseKey: "", - email: "", - password: "", - }, - wantErr: true, - }, - { - name: "error because could not get license from credentials", - args: args{ - client: clientMock, - licenseKey: "", - email: "email", - password: "password", - }, - wantErr: true, - mockFunc: func() { - HTTPPostMock = func(url, contentType string, body io.Reader) (resp *http.Response, err error) { - return nil, errors.New("something went wrong") - } - }, - }, - { - name: "error because invalid license", - args: args{ - client: clientMock, - licenseKey: "invalid license", - email: "", - password: "", - }, - wantErr: true, - mockFunc: func() { - HTTPGetMock = func(url string) (resp *http.Response, err error) { - return &http.Response{Body: ioutil.NopCloser(strings.NewReader(publicKeys[0]))}, nil - } - }, - }, - { - name: "license validated successfully", - args: args{ - client: clientMock, - licenseKey: license, - email: "", - password: "", - }, - wantErr: false, - mockFunc: func() { - HTTPGetMock = func(url string) (resp *http.Response, err error) { - return &http.Response{Body: ioutil.NopCloser(strings.NewReader(publicKeys[0]))}, nil - } - }, - wantLicense: license, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.mockFunc != nil { - tt.mockFunc() - } - _, gotLicense, err := ValidateLicense(&tt.args.client, tt.args.licenseKey, tt.args.email, tt.args.password) - if !tt.wantErr { - t.Skip() // FIXME: fix all success cases - } - if (err != nil) != tt.wantErr { - t.Errorf("ValidateLicense() error = %v, wantErr %v", err, tt.wantErr) - return - } - if gotLicense != tt.wantLicense { - t.Errorf("ValidateLicense() gotLicense = %v, want %v", gotLicense, tt.wantLicense) - } - }) - } -} diff --git a/pkg/subnet/utils.go b/pkg/subnet/utils.go new file mode 100644 index 000000000..74367e052 --- /dev/null +++ b/pkg/subnet/utils.go @@ -0,0 +1,165 @@ +// This file is part of MinIO Kubernetes Cloud +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package subnet + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + + "github.com/minio/console/cluster" + "github.com/minio/madmin-go" + mc "github.com/minio/mc/cmd" + "github.com/minio/pkg/env" +) + +const ( + subnetRespBodyLimit = 1 << 20 // 1 MiB +) + +func subnetBaseURL() string { + return env.Get(ConsoleSubnetURL, "https://subnet.min.io") +} + +func subnetRegisterURL() string { + return subnetBaseURL() + "/api/cluster/register" +} + +func subnetLoginURL() string { + return subnetBaseURL() + "/api/auth/login" +} + +func subnetOrgsURL() string { + return subnetBaseURL() + "/api/auth/organizations" +} + +func subnetMFAURL() string { + return subnetBaseURL() + "/api/auth/mfa-login" +} + +func GenerateRegToken(clusterRegInfo mc.ClusterRegistrationInfo) (string, error) { + token, e := json.Marshal(clusterRegInfo) + if e != nil { + return "", e + } + + return base64.StdEncoding.EncodeToString(token), nil +} + +func subnetAuthHeaders(authToken string) map[string]string { + return map[string]string{"Authorization": "Bearer " + authToken} +} + +func httpDo(client cluster.HTTPClientI, req *http.Request) (*http.Response, error) { + //if globalSubnetProxyURL != nil { + // client.Transport.(*http.Transport).Proxy = http.ProxyURL(globalSubnetProxyURL) + //} + return client.Do(req) +} + +func subnetReqDo(client cluster.HTTPClientI, r *http.Request, headers map[string]string) (string, error) { + for k, v := range headers { + r.Header.Add(k, v) + } + + ct := r.Header.Get("Content-Type") + if len(ct) == 0 { + r.Header.Add("Content-Type", "application/json") + } + + resp, e := httpDo(client, r) + if e != nil { + return "", e + } + + defer resp.Body.Close() + respBytes, e := ioutil.ReadAll(io.LimitReader(resp.Body, subnetRespBodyLimit)) + if e != nil { + return "", e + } + respStr := string(respBytes) + + if resp.StatusCode == http.StatusOK { + return respStr, nil + } + return respStr, fmt.Errorf("Request failed with code %d and error: %s", resp.StatusCode, respStr) +} + +func subnetGetReq(client cluster.HTTPClientI, reqURL string, headers map[string]string) (string, error) { + r, e := http.NewRequest(http.MethodGet, reqURL, nil) + if e != nil { + return "", e + } + return subnetReqDo(client, r, headers) +} + +func subnetPostReq(client cluster.HTTPClientI, reqURL string, payload interface{}, headers map[string]string) (string, error) { + body, e := json.Marshal(payload) + if e != nil { + return "", e + } + r, e := http.NewRequest(http.MethodPost, reqURL, bytes.NewReader(body)) + if e != nil { + return "", e + } + return subnetReqDo(client, r, headers) +} + +func GetClusterRegInfo(admInfo madmin.InfoMessage) mc.ClusterRegistrationInfo { + noOfPools := 1 + noOfDrives := 0 + for _, srvr := range admInfo.Servers { + if srvr.PoolNumber > noOfPools { + noOfPools = srvr.PoolNumber + } + noOfDrives += len(srvr.Disks) + } + + totalSpace, usedSpace := getDriveSpaceInfo(admInfo) + + return mc.ClusterRegistrationInfo{ + DeploymentID: admInfo.DeploymentID, + ClusterName: admInfo.DeploymentID, + UsedCapacity: admInfo.Usage.Size, + Info: mc.ClusterInfo{ + MinioVersion: admInfo.Servers[0].Version, + NoOfServerPools: noOfPools, + NoOfServers: len(admInfo.Servers), + NoOfDrives: noOfDrives, + TotalDriveSpace: totalSpace, + UsedDriveSpace: usedSpace, + NoOfBuckets: admInfo.Buckets.Count, + NoOfObjects: admInfo.Objects.Count, + }, + } +} + +func getDriveSpaceInfo(admInfo madmin.InfoMessage) (uint64, uint64) { + total := uint64(0) + used := uint64(0) + for _, srvr := range admInfo.Servers { + for _, d := range srvr.Disks { + total += d.TotalSpace + used += d.UsedSpace + } + } + return total, used +} diff --git a/portal-ui/src/common/SecureComponent/permissions.ts b/portal-ui/src/common/SecureComponent/permissions.ts index be6ba1daa..59cf6ee36 100644 --- a/portal-ui/src/common/SecureComponent/permissions.ts +++ b/portal-ui/src/common/SecureComponent/permissions.ts @@ -386,10 +386,17 @@ export const IAM_PAGES_PERMISSIONS = { IAM_SCOPES.ADMIN_SERVER_INFO, ], [IAM_PAGES.TOOLS_SPEEDTEST]: [IAM_SCOPES.ADMIN_HEALTH_INFO], - [IAM_PAGES.REGISTER_SUPPORT]: [IAM_SCOPES.ADMIN_HEALTH_INFO], + [IAM_PAGES.REGISTER_SUPPORT]: [ + IAM_SCOPES.ADMIN_SERVER_INFO, + IAM_SCOPES.ADMIN_CONFIG_UPDATE, + ], [IAM_PAGES.CALL_HOME]: [IAM_SCOPES.ADMIN_HEALTH_INFO], [IAM_PAGES.PROFILE]: [IAM_SCOPES.ADMIN_HEALTH_INFO], [IAM_PAGES.HEALTH]: [IAM_SCOPES.ADMIN_HEALTH_INFO], + [IAM_PAGES.LICENSE]: [ + IAM_SCOPES.ADMIN_SERVER_INFO, + IAM_SCOPES.ADMIN_CONFIG_UPDATE, + ], }; export const S3_ALL_RESOURCES = "arn:aws:s3:::*"; diff --git a/portal-ui/src/icons/OfflineRegistrationBackIcon.tsx b/portal-ui/src/icons/OfflineRegistrationBackIcon.tsx new file mode 100644 index 000000000..41aa84dc6 --- /dev/null +++ b/portal-ui/src/icons/OfflineRegistrationBackIcon.tsx @@ -0,0 +1,32 @@ +import * as React from "react"; +import { SVGProps } from "react"; + +const OfflineRegistrationBackIcon = (props: SVGProps) => ( + + + + + + + + +); + +export default OfflineRegistrationBackIcon; diff --git a/portal-ui/src/icons/OfflineRegistrationIcon.tsx b/portal-ui/src/icons/OfflineRegistrationIcon.tsx new file mode 100644 index 000000000..6df83ea8b --- /dev/null +++ b/portal-ui/src/icons/OfflineRegistrationIcon.tsx @@ -0,0 +1,71 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import * as React from "react"; +import { SVGProps } from "react"; + +const OfflineRegistrationIcon = (props: SVGProps) => ( + + + + + + + + + + + + + + + + + + +); + +export default OfflineRegistrationIcon; diff --git a/portal-ui/src/icons/OnlineRegistrationBackIcon.tsx b/portal-ui/src/icons/OnlineRegistrationBackIcon.tsx new file mode 100644 index 000000000..2b115aa4f --- /dev/null +++ b/portal-ui/src/icons/OnlineRegistrationBackIcon.tsx @@ -0,0 +1,88 @@ +import * as React from "react"; +import { SVGProps } from "react"; + +const OnlineRegistrationBackIcon = (props: SVGProps) => ( + + + + + + + + + + + + + + + + + + + + + + + + + +); + +export default OnlineRegistrationBackIcon; diff --git a/portal-ui/src/icons/OnlineRegistrationIcon.tsx b/portal-ui/src/icons/OnlineRegistrationIcon.tsx new file mode 100644 index 000000000..f1efe38d2 --- /dev/null +++ b/portal-ui/src/icons/OnlineRegistrationIcon.tsx @@ -0,0 +1,109 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import * as React from "react"; +import { SVGProps } from "react"; + +const OnlineRegistrationIcon = (props: SVGProps) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + +); + +export default OnlineRegistrationIcon; diff --git a/portal-ui/src/icons/VerifiedIcon.tsx b/portal-ui/src/icons/VerifiedIcon.tsx new file mode 100644 index 000000000..f970e18c4 --- /dev/null +++ b/portal-ui/src/icons/VerifiedIcon.tsx @@ -0,0 +1,44 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2022 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import * as React from "react"; +import { SVGProps } from "react"; + +const VerifiedIcon = (props: SVGProps) => ( + + + + + + + + + + +); + +export default VerifiedIcon; diff --git a/portal-ui/src/screens/Console/Buckets/types.tsx b/portal-ui/src/screens/Console/Buckets/types.tsx index 83258f963..3f21309fb 100644 --- a/portal-ui/src/screens/Console/Buckets/types.tsx +++ b/portal-ui/src/screens/Console/Buckets/types.tsx @@ -144,12 +144,6 @@ export interface ChangeUserPasswordRequest { newSecretKey: string; } -export interface SubscriptionActivateRequest { - license: string; - email: string; - password: string; -} - export interface IRemoteBucket { name: string; accessKey: string; diff --git a/portal-ui/src/screens/Console/Common/FormComponents/InputBoxWrapper/InputBoxWrapper.tsx b/portal-ui/src/screens/Console/Common/FormComponents/InputBoxWrapper/InputBoxWrapper.tsx index b96cd8dea..4f21174a8 100644 --- a/portal-ui/src/screens/Console/Common/FormComponents/InputBoxWrapper/InputBoxWrapper.tsx +++ b/portal-ui/src/screens/Console/Common/FormComponents/InputBoxWrapper/InputBoxWrapper.tsx @@ -34,6 +34,7 @@ import { tooltipHelper, } from "../common/styleLibrary"; import HelpIcon from "../../../../../icons/HelpIcon"; +import clsx from "clsx"; interface InputBoxProps { label: string; @@ -60,6 +61,7 @@ interface InputBoxProps { noLabelMinWidth?: boolean; pattern?: string; autoFocus?: boolean; + className?: string; } const styles = (theme: Theme) => @@ -130,6 +132,7 @@ const InputBoxWrapper = ({ pattern = "", autoFocus = false, classes, + className = "", }: InputBoxProps) => { let inputProps: any = { "data-index": index, ...extraInputProps }; @@ -149,9 +152,10 @@ const InputBoxWrapper = ({ {label !== "" && ( . -import React, { useState } from "react"; +import React, { Fragment, useState } from "react"; import { connect } from "react-redux"; import { Theme } from "@mui/material/styles"; import createStyles from "@mui/styles/createStyles"; import withStyles from "@mui/styles/withStyles"; -import { LinearProgress, TextField } from "@mui/material"; +import { LinearProgress } from "@mui/material"; import Grid from "@mui/material/Grid"; import Typography from "@mui/material/Typography"; import Button from "@mui/material/Button"; import { containerForHeader } from "../Common/FormComponents/common/styleLibrary"; -import { SubscriptionActivateRequest } from "../Buckets/types"; +import { + SubnetLoginRequest, + SubnetLoginResponse, + SubnetLoginWithMFARequest, + SubnetOrganization, + SubnetRegisterRequest, + SubnetRegTokenResponse, +} from "./types"; import { setModalErrorSnackMessage } from "../../../actions"; import { ErrorResponseHandler } from "../../../common/types"; import ModalWrapper from "../Common/ModalWrapper/ModalWrapper"; @@ -33,23 +40,21 @@ import api from "../../../common/api"; import PersonOutlineOutlinedIcon from "@mui/icons-material/PersonOutlineOutlined"; import LockOutlinedIcon from "@mui/icons-material/LockOutlined"; +import { formFieldStyles } from "../Common/FormComponents/common/styleLibrary"; +import RadioGroupSelector from "../Common/FormComponents/RadioGroupSelector/RadioGroupSelector"; +import Link from "@mui/material/Link"; + const styles = (theme: Theme) => createStyles({ - subnetLicenseKey: { - padding: "10px 10px 10px 0px", - borderRight: "1px solid rgba(0, 0, 0, 0.12)", - opacity: 0.5, - "&:hover": { opacity: 1 }, - }, - subnetLoginForm: { - padding: "10px 0px 10px 10px", - opacity: 0.5, - "&:hover": { opacity: 1 }, - }, + subnetLicenseKey: {}, + subnetLoginForm: {}, licenseKeyField: {}, pageTitle: { marginBottom: 20, }, + registrationMode: { + cursor: "pointer", + }, button: { textTransform: "none", fontSize: 15, @@ -64,6 +69,7 @@ const styles = (theme: Theme) => fontWeight: 700, marginLeft: 15, }, + ...formFieldStyles, ...containerForHeader(theme.spacing(4)), }); @@ -83,36 +89,288 @@ const ActivationModal = ({ const [license, setLicense] = useState(""); const [subnetPassword, setSubnetPassword] = useState(""); const [subnetEmail, setSubnetEmail] = useState(""); + const [subnetMFAToken, setSubnetMFAToken] = useState(""); + const [subnetOTP, setSubnetOTP] = useState(""); + const [subnetAccessToken, setSubnetAccessToken] = useState(""); + const [selectedSubnetOrganisation, setSelectedSubnetOrganisation] = + useState(""); + const [subnetRegToken, setSubnetRegToken] = useState(""); + const [subnetOrganizations, setSubnetOrganizations] = useState< + SubnetOrganization[] + >([]); + const [onlineActivation, setOnlineActivation] = useState(true); + const [loading, setLoading] = useState(false); - const activateProduct = () => { + const clearForm = () => { + setLicense(""); + setSubnetPassword(""); + setSubnetEmail(""); + setSubnetMFAToken(""); + setSubnetOTP(""); + }; + + const fetchSubnetRegToken = () => { + if (loading || subnetRegToken) { + return; + } + setLoading(true); + api + .invoke("GET", "/api/v1/subnet/registration-token") + .then((resp: SubnetRegTokenResponse) => { + setLoading(false); + if (resp && resp.regToken) { + setSubnetRegToken(resp.regToken); + } + }) + .catch((err: ErrorResponseHandler) => { + setLoading(false); + setModalErrorSnackMessage(err); + }); + }; + + const subnetRegister = () => { if (loading) { return; } setLoading(true); - let request: SubscriptionActivateRequest = { - license: license, - email: subnetEmail, - password: subnetPassword, + if (subnetAccessToken && selectedSubnetOrganisation) { + const request: SubnetRegisterRequest = { + token: subnetAccessToken, + account_id: selectedSubnetOrganisation, + }; + api + .invoke("POST", "/api/v1/subnet/register", request) + .then(() => { + setLoading(false); + clearForm(); + closeModal(); + }) + .catch((err: ErrorResponseHandler) => { + setLoading(false); + setModalErrorSnackMessage(err); + }); + } + }; + + const subnetLoginWithMFA = () => { + if (loading) { + return; + } + setLoading(true); + const request: SubnetLoginWithMFARequest = { + username: subnetEmail, + otp: subnetOTP, + mfa_token: subnetMFAToken, }; api - .invoke("POST", "/api/v1/subscription/validate", request) - .then(() => { + .invoke("POST", "/api/v1/subnet/login/mfa", request) + .then((resp: SubnetLoginResponse) => { setLoading(false); - setLicense(""); - setSubnetPassword(""); - setSubnetEmail(""); - closeModal(); + if (resp && resp.access_token && resp.organizations.length > 0) { + setSubnetAccessToken(resp.access_token); + setSubnetOrganizations(resp.organizations); + setSelectedSubnetOrganisation( + resp.organizations[0].accountId.toString() + ); + } }) .catch((err: ErrorResponseHandler) => { setLoading(false); - setLicense(""); - setSubnetPassword(""); - setSubnetEmail(""); + setSubnetOTP(""); setModalErrorSnackMessage(err); }); }; + const subnetLogin = () => { + if (loading) { + return; + } + setLoading(true); + let request: SubnetLoginRequest = { + username: subnetEmail, + password: subnetPassword, + apiKey: license, + }; + api + .invoke("POST", "/api/v1/subnet/login", request) + .then((resp: SubnetLoginResponse) => { + setLoading(false); + if (resp && resp.registered) { + clearForm(); + closeModal(); + } else if (resp && resp.mfa_token) { + setSubnetMFAToken(resp.mfa_token); + } else if (resp && resp.access_token && resp.organizations.length > 0) { + setSubnetAccessToken(resp.access_token); + setSubnetOrganizations(resp.organizations); + setSelectedSubnetOrganisation( + resp.organizations[0].accountId.toString() + ); + } + }) + .catch((err: ErrorResponseHandler) => { + setLoading(false); + clearForm(); + setModalErrorSnackMessage(err); + }); + }; + + let clusterRegistrationForm: JSX.Element; + if (subnetAccessToken && subnetOrganizations.length > 0) { + clusterRegistrationForm = ( + + + + Register MinIO cluster + + + + ) => { + setSelectedSubnetOrganisation(e.target.value as string); + }} + selectorOptions={subnetOrganizations.map((organisation) => ({ + value: organisation.accountId.toString(), + label: organisation.company, + }))} + /> + + + + + + ); + } else if (subnetMFAToken) { + clusterRegistrationForm = ( + + + + Two-Factor Authentication + + + Please enter the 6-digit verification code that was sent to your + email address. This code will be valid for 5 minutes. + + + + } + id="subnet-otp" + name="subnet-otp" + onChange={(event: React.ChangeEvent) => { + setSubnetOTP(event.target.value); + }} + placeholder="" + label="" + type="text" + value={subnetOTP} + /> + + + + + + ); + } else { + clusterRegistrationForm = ( + + + } + id="subnet-email" + name="subnet-email" + onChange={(event: React.ChangeEvent) => { + setSubnetEmail(event.target.value); + }} + placeholder="email" + label="" + type="text" + value={subnetEmail} + /> + + + } + id="subnet-password" + name="subnet-password" + onChange={(event: React.ChangeEvent) => { + setSubnetPassword(event.target.value); + }} + placeholder="password" + label="" + type="password" + value={subnetPassword} + /> + + + + + + + { + fetchSubnetRegToken(); + setOnlineActivation(false); + }} + > + Offline Activation + + + + ); + } + return open ? ( - - - - Enter your license key here - + {onlineActivation ? ( + + {clusterRegistrationForm} - ) => - setLicense(event.target.value) - } - fullWidth - className={classes.licenseKeyField} - variant="outlined" - /> -
-
- -
- - - + ) : ( + + + Step 1: Copy the following registration token + + } - id="subnet-email" - name="subnet-email" - onChange={(event: React.ChangeEvent) => { - setSubnetEmail(event.target.value); - }} - placeholder="email" + onChange={() => {}} + id="registration-token" + name="registration-token" + placeholder="" label="" type="text" - value={subnetEmail} + value={subnetRegToken} + disabled /> - - } - id="subnet-password" - name="subnet-password" - onChange={(event: React.ChangeEvent) => { - setSubnetPassword(event.target.value); - }} - placeholder="password" - label="" - type="password" - value={subnetPassword} - /> - - + + + + Step 2: Use the previous token to register your cluster at:{" "} + + https://subnet.min.io/cluster/register + + + + Step 3: Enter the API key generated by SUBNET + + + ) => + setLicense(event.target.value) + } + id="api-key" + name="api-key" + placeholder="" + label="" + type="text" + /> + + + - + + setOnlineActivation(true)} + > + Online Activation + + - + )} {loading && ( diff --git a/portal-ui/src/screens/Console/License/License.tsx b/portal-ui/src/screens/Console/License/License.tsx index a95f884c7..1a0971c1e 100644 --- a/portal-ui/src/screens/Console/License/License.tsx +++ b/portal-ui/src/screens/Console/License/License.tsx @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -import React, { Fragment, useEffect, useState } from "react"; +import React, { Fragment, useCallback, useEffect, useState } from "react"; import { connect } from "react-redux"; import { Theme } from "@mui/material/styles"; import createStyles from "@mui/styles/createStyles"; @@ -26,7 +26,7 @@ import Button from "@mui/material/Button"; import Moment from "react-moment"; import Typography from "@mui/material/Typography"; import CheckCircleIcon from "@mui/icons-material/CheckCircle"; -import { LicenseInfo } from "./types"; +import { SubnetInfo } from "./types"; import { AppState } from "../../../store"; import { niceBytes } from "../../../common/utils"; import { ErrorResponseHandler } from "../../../common/types"; @@ -37,6 +37,12 @@ import ActivationModal from "./ActivationModal"; import LicenseModal from "./LicenseModal"; import api from "../../../common/api"; import { LicenseIcon } from "../../../icons"; +import { hasPermission } from "../../../common/SecureComponent/SecureComponent"; +import { + CONSOLE_UI_RESOURCE, + IAM_PAGES, + IAM_PAGES_PERMISSIONS, +} from "../../../common/SecureComponent/permissions"; const mapState = (state: AppState) => ({ operatorMode: state.system.operatorMode, @@ -55,7 +61,6 @@ const styles = (theme: Theme) => fontWeight: "bold", "& ul": { listStyleType: "square", - // listStyleType: "none", "& li": { float: "left", fontSize: 14, @@ -301,36 +306,64 @@ interface ILicenseProps { } const License = ({ classes, operatorMode }: ILicenseProps) => { + const [activateProductModal, setActivateProductModal] = + useState(false); + + const [licenseModal, setLicenseModal] = useState(false); + + const [licenseInfo, setLicenseInfo] = useState(); + const [currentPlanID, setCurrentPlanID] = useState(0); + const [loadingLicenseInfo, setLoadingLicenseInfo] = useState(false); + const [initialLicenseLoading, setInitialLicenseLoading] = + useState(true); + const [loadingRefreshLicense, setLoadingRefreshLicense] = + useState(false); + + const getSubnetInfo = hasPermission( + CONSOLE_UI_RESOURCE, + IAM_PAGES_PERMISSIONS[IAM_PAGES.LICENSE], + true + ); + const closeModalAndFetchLicenseInfo = () => { setActivateProductModal(false); fetchLicenseInfo(); }; - const fetchLicenseInfo = () => { - setLoadingLicenseInfo(true); - api - .invoke("GET", `/api/v1/subscription/info`) - .then((res: LicenseInfo) => { - if (res) { - if (res.plan === "STANDARD") { - setCurrentPlanID(1); - } else if (res.plan === "ENTERPRISE") { - setCurrentPlanID(2); - } else { - setCurrentPlanID(1); + + const fetchLicenseInfo = useCallback(() => { + if (loadingLicenseInfo) { + return; + } + if (getSubnetInfo) { + setLoadingLicenseInfo(true); + api + .invoke("GET", `/api/v1/subnet/info`) + .then((res: SubnetInfo) => { + if (res) { + if (res.plan === "STANDARD") { + setCurrentPlanID(1); + } else if (res.plan === "ENTERPRISE") { + setCurrentPlanID(2); + } else { + setCurrentPlanID(1); + } + setLicenseInfo(res); } - setLicenseInfo(res); - } - setLoadingLicenseInfo(false); - }) - .catch(() => { - setLoadingLicenseInfo(false); - }); - }; + setLoadingLicenseInfo(false); + }) + .catch(() => { + setLoadingLicenseInfo(false); + }); + } else { + setLoadingLicenseInfo(false); + } + }, [loadingLicenseInfo, getSubnetInfo]); + const refreshLicense = () => { setLoadingRefreshLicense(true); api .invoke("POST", `/api/v1/subscription/refresh`, {}) - .then((res: LicenseInfo) => { + .then((res: SubnetInfo) => { if (res) { if (res.plan === "STANDARD") { setCurrentPlanID(1); @@ -348,20 +381,12 @@ const License = ({ classes, operatorMode }: ILicenseProps) => { }); }; - const [activateProductModal, setActivateProductModal] = - useState(false); - - const [licenseModal, setLicenseModal] = useState(false); - - const [licenseInfo, setLicenseInfo] = useState(); - const [currentPlanID, setCurrentPlanID] = useState(0); - const [loadingLicenseInfo, setLoadingLicenseInfo] = useState(true); - const [loadingRefreshLicense, setLoadingRefreshLicense] = - useState(false); - useEffect(() => { - fetchLicenseInfo(); - }, []); + if (initialLicenseLoading) { + fetchLicenseInfo(); + setInitialLicenseLoading(false); + } + }, [fetchLicenseInfo, initialLicenseLoading, setInitialLicenseLoading]); if (loadingLicenseInfo) { return ( @@ -653,14 +678,8 @@ const License = ({ classes, operatorMode }: ILicenseProps) => {
- Pricing + Are you already a customer? - - The MinIO Subscription Network provides exclusive benefits across - licensing, operations and support. See the pricing table below for - more information. - -
@@ -669,12 +688,10 @@ const License = ({ classes, operatorMode }: ILicenseProps) => {
- {operatorMode ? ( - closeModalAndFetchLicenseInfo()} - /> - ) : null} + closeModalAndFetchLicenseInfo()} + /> @@ -892,8 +909,7 @@ const License = ({ classes, operatorMode }: ILicenseProps) => { : button.text} - {operatorMode && - button.text === "Subscribe" && + {button.text === "Subscribe" && !( licenseInfo && licenseInfo.plan.toLowerCase() === @@ -907,7 +923,7 @@ const License = ({ classes, operatorMode }: ILicenseProps) => { setActivateProductModal(true); }} > - Activate + Register )} diff --git a/portal-ui/src/screens/Console/License/types.tsx b/portal-ui/src/screens/Console/License/types.tsx index 27a17d74b..233a756bf 100644 --- a/portal-ui/src/screens/Console/License/types.tsx +++ b/portal-ui/src/screens/Console/License/types.tsx @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -export interface LicenseInfo { +export interface SubnetInfo { account_id: number; email: string; expires_at: string; @@ -22,3 +22,40 @@ export interface LicenseInfo { storage_capacity: number; organization: string; } + +export interface SubnetLoginRequest { + username?: string; + password?: string; + apiKey?: string; +} + +export interface SubnetRegisterRequest { + token: string; + account_id: string; +} + +export interface SubnetOrganization { + userId: number; + accountId: number; + subscriptionStatus: string; + isAccountOwner: boolean; + shortName: string; + company: string; +} + +export interface SubnetLoginResponse { + registered: boolean; + mfa_token: string; + access_token: string; + organizations: SubnetOrganization[]; +} + +export interface SubnetLoginWithMFARequest { + username: string; + otp: string; + mfa_token: string; +} + +export interface SubnetRegTokenResponse { + regToken: string; +} diff --git a/portal-ui/src/screens/Console/License/utils.ts b/portal-ui/src/screens/Console/License/utils.ts index 1b90527f9..0336cac32 100644 --- a/portal-ui/src/screens/Console/License/utils.ts +++ b/portal-ui/src/screens/Console/License/utils.ts @@ -135,14 +135,14 @@ export const planButtons = [ { id: 1, text: "Subscribe", - text2: "Upgrade", + text2: "Sign up", link: "https://subnet.min.io/subscription", plan: "standard", }, { id: 2, text: "Subscribe", - text2: "Upgrade", + text2: "Sign up", link: "https://subnet.min.io/subscription", plan: "enterprise", }, diff --git a/portal-ui/src/screens/Console/Support/Register.tsx b/portal-ui/src/screens/Console/Support/Register.tsx new file mode 100644 index 000000000..2b65a3f68 --- /dev/null +++ b/portal-ui/src/screens/Console/Support/Register.tsx @@ -0,0 +1,618 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import { Theme } from "@mui/material/styles"; +import createStyles from "@mui/styles/createStyles"; +import { + actionsTray, + searchField, + containerForHeader, +} from "../Common/FormComponents/common/styleLibrary"; +import withStyles from "@mui/styles/withStyles"; +import { Button, Grid, Link, Typography } from "@mui/material"; +import PageHeader from "../Common/PageHeader/PageHeader"; +import PageLayout from "../Common/Layout/PageLayout"; +import React, { Fragment, useCallback, useEffect, useState } from "react"; +import { CopyIcon, UsersIcon } from "../../../icons"; +import RemoveRedEyeIcon from "@mui/icons-material/RemoveRedEye"; +import VisibilityOffIcon from "@mui/icons-material/VisibilityOff"; +import OnlineRegistrationIcon from "../../../icons/OnlineRegistrationIcon"; +import OfflineRegistrationBackIcon from "../../../icons/OfflineRegistrationBackIcon"; +import OfflineRegistrationIcon from "../../../icons/OfflineRegistrationIcon"; +import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper"; +import clsx from "clsx"; +import OnlineRegistrationBackIcon from "../../../icons/OnlineRegistrationBackIcon"; +import api from "../../../common/api"; + +import { + SubnetInfo, + SubnetLoginRequest, + SubnetLoginResponse, + SubnetLoginWithMFARequest, + SubnetOrganization, + SubnetRegisterRequest, + SubnetRegTokenResponse, +} from "../License/types"; +import { ErrorResponseHandler } from "../../../common/types"; +import LockOutlinedIcon from "@mui/icons-material/LockOutlined"; +import SelectWrapper from "../Common/FormComponents/SelectWrapper/SelectWrapper"; +import { hasPermission } from "../../../common/SecureComponent/SecureComponent"; +import { + CONSOLE_UI_RESOURCE, + IAM_PAGES, + IAM_PAGES_PERMISSIONS, +} from "../../../common/SecureComponent/permissions"; +import VerifiedIcon from "../../../icons/VerifiedIcon"; +import { connect } from "react-redux"; +import { setErrorSnackMessage } from "../../../actions"; + +interface IRegister { + classes: any; + displayErrorMessage: typeof setErrorSnackMessage; +} + +const styles = (theme: Theme) => + createStyles({ + loading: { + paddingTop: 8, + paddingLeft: 40, + }, + buttons: { + justifyContent: "flex-start", + gap: 20, + }, + localMessage: { + fontSize: 24, + color: "#07193E", + fontWeight: "bold", + textAlign: "center", + marginBottom: 10, + }, + registerActivationIcon: { + color: theme.palette.primary.main, + fontSize: 16, + fontWeight: "bold", + marginBottom: 20, + "& .min-icon": { + width: 32.12, + height: 25, + marginRight: 10, + verticalAlign: "middle", + }, + }, + registerActivationMode: { + textAlign: "right", + "& a": { + cursor: "pointer", + }, + }, + subnetDescription: { + textAlign: "left", + Font: "normal normal normal 14px/17px Lato", + letterSpacing: 0, + color: "#000000", + "& span": { + fontWeight: "bold", + }, + }, + subnetLoginInputBoxWrapper: { + paddingRight: 20, + }, + registeredStatus: { + border: "1px solid #E2E2E2", + padding: "24px 24px 24px 24px", + borderRadius: 2, + marginBottom: 25, + backgroundColor: "#FBFAFA", + "& .min-icon": { + width: 20, + height: 20, + marginLeft: 48, + marginRight: 13, + verticalAlign: "middle", + marginTop: -3, + }, + "& span": { + fontWeight: "bold", + }, + }, + offlineRegisterButton: { + textAlign: "right", + paddingRight: 20, + }, + copyInputBox: { + "& button": { + border: "1px solid #5E5E5E", + borderRadius: 2, + }, + }, + link: { + color: "#2781B0", + }, + ...actionsTray, + ...searchField, + ...containerForHeader(theme.spacing(4)), + }); + +const Register = ({ classes, displayErrorMessage }: IRegister) => { + const [license, setLicense] = useState(""); + const [subnetPassword, setSubnetPassword] = useState(""); + const [subnetEmail, setSubnetEmail] = useState(""); + const [subnetMFAToken, setSubnetMFAToken] = useState(""); + const [subnetOTP, setSubnetOTP] = useState(""); + const [subnetAccessToken, setSubnetAccessToken] = useState(""); + const [selectedSubnetOrganization, setSelectedSubnetOrganization] = + useState(""); + const [subnetRegToken, setSubnetRegToken] = useState(""); + const [subnetOrganizations, setSubnetOrganizations] = useState< + SubnetOrganization[] + >([]); + const [showPassword, setShowPassword] = useState(false); + const [onlineActivation, setOnlineActivation] = useState(true); + const [loading, setLoading] = useState(false); + const [loadingLicenseInfo, setLoadingLicenseInfo] = useState(false); + const [clusterRegistered, setClusterRegistered] = useState(false); + const [initialLicenseLoading, setInitialLicenseLoading] = + useState(true); + + const clearForm = () => { + setSubnetAccessToken(""); + setSelectedSubnetOrganization(""); + setSubnetRegToken(""); + setShowPassword(false); + setOnlineActivation(true); + setSubnetOrganizations([]); + setLicense(""); + setSubnetPassword(""); + setSubnetEmail(""); + setSubnetMFAToken(""); + setSubnetOTP(""); + }; + + const getSubnetInfo = hasPermission( + CONSOLE_UI_RESOURCE, + IAM_PAGES_PERMISSIONS[IAM_PAGES.LICENSE], + true + ); + + const fetchLicenseInfo = useCallback(() => { + if (loadingLicenseInfo) { + return; + } + if (getSubnetInfo) { + setLoadingLicenseInfo(true); + api + .invoke("GET", `/api/v1/subnet/info`) + .then((res: SubnetInfo) => { + setClusterRegistered(true); + setLoadingLicenseInfo(false); + }) + .catch((err: ErrorResponseHandler) => { + displayErrorMessage(err); + setClusterRegistered(false); + setLoadingLicenseInfo(false); + }); + } else { + setLoadingLicenseInfo(false); + } + }, [loadingLicenseInfo, getSubnetInfo, displayErrorMessage]); + + const fetchSubnetRegToken = () => { + if (loading || subnetRegToken) { + return; + } + setLoading(true); + api + .invoke("GET", "/api/v1/subnet/registration-token") + .then((resp: SubnetRegTokenResponse) => { + setLoading(false); + if (resp && resp.regToken) { + setSubnetRegToken(resp.regToken); + } + }) + .catch((err: ErrorResponseHandler) => { + displayErrorMessage(err); + setLoading(false); + }); + }; + + const callRegister = (token: string, account_id: string) => { + const request: SubnetRegisterRequest = { + token: token, + account_id: account_id, + }; + api + .invoke("POST", "/api/v1/subnet/register", request) + .then(() => { + setLoading(false); + clearForm(); + fetchLicenseInfo(); + }) + .catch((err: ErrorResponseHandler) => { + displayErrorMessage(err); + setLoading(false); + }); + }; + const subnetRegister = () => { + if (loading) { + return; + } + setLoading(true); + if (subnetAccessToken && selectedSubnetOrganization) { + callRegister(subnetAccessToken, selectedSubnetOrganization); + } + }; + + const subnetLoginWithMFA = () => { + if (loading) { + return; + } + setLoading(true); + const request: SubnetLoginWithMFARequest = { + username: subnetEmail, + otp: subnetOTP, + mfa_token: subnetMFAToken, + }; + api + .invoke("POST", "/api/v1/subnet/login/mfa", request) + .then((resp: SubnetLoginResponse) => { + setLoading(false); + if (resp && resp.access_token && resp.organizations.length > 0) { + if (resp.organizations.length === 1) { + callRegister( + resp.access_token, + resp.organizations[0].accountId.toString() + ); + } else { + setSubnetAccessToken(resp.access_token); + setSubnetOrganizations(resp.organizations); + setSelectedSubnetOrganization( + resp.organizations[0].accountId.toString() + ); + } + } + }) + .catch((err: ErrorResponseHandler) => { + displayErrorMessage(err); + setLoading(false); + setSubnetOTP(""); + }); + }; + + const subnetLogin = () => { + if (loading) { + return; + } + setLoading(true); + let request: SubnetLoginRequest = { + username: subnetEmail, + password: subnetPassword, + apiKey: license, + }; + api + .invoke("POST", "/api/v1/subnet/login", request) + .then((resp: SubnetLoginResponse) => { + setLoading(false); + if (resp && resp.registered) { + clearForm(); + fetchLicenseInfo(); + } else if (resp && resp.mfa_token) { + setSubnetMFAToken(resp.mfa_token); + } else if (resp && resp.access_token && resp.organizations.length > 0) { + setSubnetAccessToken(resp.access_token); + setSubnetOrganizations(resp.organizations); + setSelectedSubnetOrganization( + resp.organizations[0].accountId.toString() + ); + } + }) + .catch((err: ErrorResponseHandler) => { + displayErrorMessage(err); + setLoading(false); + clearForm(); + }); + }; + + useEffect(() => { + if (initialLicenseLoading) { + fetchLicenseInfo(); + setInitialLicenseLoading(false); + } + }, [fetchLicenseInfo, initialLicenseLoading, setInitialLicenseLoading]); + + const title = onlineActivation ? ( + + + Online Activation SUBNET License + + ) : ( + + + Offline Activating SUBNET License + + ); + + let clusterRegistrationForm: JSX.Element; + + if (onlineActivation) { + if (subnetAccessToken && subnetOrganizations.length > 0) { + clusterRegistrationForm = ( + + + Register MinIO cluster + +
+ + + setSelectedSubnetOrganization(e.target.value as string) + } + label="Select an organization" + value={selectedSubnetOrganization} + options={subnetOrganizations.map((organization) => ({ + label: organization.company, + value: organization.accountId.toString(), + }))} + /> + + + + +
+ ); + } else if (subnetMFAToken) { + clusterRegistrationForm = ( + + + Two-Factor Authentication + + Please enter the 6-digit verification code that was sent to your + email address. This code will be valid for 5 minutes. + + +
+ + } + id="subnet-otp" + name="subnet-otp" + onChange={(event: React.ChangeEvent) => + setSubnetOTP(event.target.value) + } + placeholder="" + label="" + value={subnetOTP} + /> + + +
+ ); + } else { + clusterRegistrationForm = ( + + + The MinIO Subscription Network (SUBNET for short) is a simple, yet + powerful software stack that ensures commercial customers are + getting the very best out of their MinIO instances. SUBNET is priced + on capacity - just like cloud storage and comes in two + configurations, Standard and Enterprise. + +
+ + You can use your credentials from SUBNET to register.{" "} + + Learn more about SUBNET + + +
+ + ) => + setSubnetEmail(event.target.value) + } + label="Email" + value={subnetEmail} + noLabelMinWidth + overlayIcon={} + /> + ) => + setSubnetPassword(event.target.value) + } + label="Password" + type={showPassword ? "text" : "password"} + value={subnetPassword} + noLabelMinWidth + overlayIcon={ + showPassword ? : + } + overlayAction={() => setShowPassword(!showPassword)} + /> + + +
+ ); + } + } else { + clusterRegistrationForm = ( + + + Step 1: Copy the following registration token + + + {}} + value={subnetRegToken} + disabled + overlayIcon={} + overlayAction={() => navigator.clipboard.writeText(subnetRegToken)} + /> + + + Step 2: Use the previous token to register your cluster + at:{" "} + + https://subnet.min.io/cluster/register + + +
+ + Step 3: Enter the API key generated by SUBNET + + + ) => + setLicense(event.target.value) + } + id="api-key" + name="api-key" + placeholder="" + label="" + type="text" + /> + + + + +
+ ); + } + + return ( + + + + + {clusterRegistered && ( + + + Register Status: + + Registered + + + )} + + + {title} + + + {onlineActivation ? ( + + + { + fetchSubnetRegToken(); + setOnlineActivation(!onlineActivation); + }} + > + Offline Activation + + + ) : ( + + + setOnlineActivation(!onlineActivation)} + > + Back to Online Activation + + + )} + + + + {clusterRegistrationForm} + + + + ); +}; + +const connector = connect(null, { + displayErrorMessage: setErrorSnackMessage, +}); + +export default withStyles(styles)(connector(Register)); diff --git a/portal-ui/src/screens/Console/Tenants/ListTenants/types.ts b/portal-ui/src/screens/Console/Tenants/ListTenants/types.ts index eaa1399b6..3fe726ddb 100644 --- a/portal-ui/src/screens/Console/Tenants/ListTenants/types.ts +++ b/portal-ui/src/screens/Console/Tenants/ListTenants/types.ts @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -import { LicenseInfo } from "../../License/types"; +import { SubnetInfo } from "../../License/types"; import { IAffinityModel } from "../../../../common/types"; import { NodeMaxAllocatableResources } from "../types"; @@ -114,7 +114,7 @@ export interface ITenant { capacity_usage?: number; // computed total_capacity: string; - subnet_license: LicenseInfo; + subnet_license: SubnetInfo; total_instances?: number; total_volumes?: number; } diff --git a/portal-ui/src/screens/Console/Tenants/TenantDetails/SubnetLicenseTenant.tsx b/portal-ui/src/screens/Console/Tenants/TenantDetails/SubnetLicenseTenant.tsx index 5a6af631c..df8af0ce8 100644 --- a/portal-ui/src/screens/Console/Tenants/TenantDetails/SubnetLicenseTenant.tsx +++ b/portal-ui/src/screens/Console/Tenants/TenantDetails/SubnetLicenseTenant.tsx @@ -26,15 +26,15 @@ import Moment from "react-moment"; import { Link } from "react-router-dom"; import Paper from "@mui/material/Paper"; import { ITenant } from "../ListTenants/types"; -import { LicenseInfo } from "../../License/types"; import RBIconButton from "../../Buckets/BucketDetails/SummaryItems/RBIconButton"; +import { SubnetInfo } from "../../License/types"; interface ISubnetLicenseTenant { classes: any; tenant: ITenant | null; loadingActivateProduct: any; loadingLicenseInfo: boolean; - licenseInfo: LicenseInfo | undefined; + licenseInfo: SubnetInfo | undefined; activateProduct: any; } diff --git a/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantLicense.tsx b/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantLicense.tsx index e231fe318..67a99ea94 100644 --- a/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantLicense.tsx +++ b/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantLicense.tsx @@ -26,7 +26,7 @@ import { tenantDetailsStyles, } from "../../Common/FormComponents/common/styleLibrary"; import { ITenant } from "../ListTenants/types"; -import { LicenseInfo } from "../../License/types"; +import { SubnetInfo } from "../../License/types"; import { setErrorSnackMessage } from "../../../../actions"; import { AppState } from "../../../../store"; import { setTenantDetailsLoad } from "../actions"; @@ -56,7 +56,7 @@ const TenantLicense = ({ loadingTenant, setTenantDetailsLoad, }: ITenantLicense) => { - const [licenseInfo, setLicenseInfo] = useState(); + const [licenseInfo, setLicenseInfo] = useState(); const [loadingLicenseInfo, setLoadingLicenseInfo] = useState(true); const [loadingActivateProduct, setLoadingActivateProduct] = useState(false); @@ -87,7 +87,7 @@ const TenantLicense = ({ if (loadingLicenseInfo) { api .invoke("GET", `/api/v1/subscription/info`) - .then((res: LicenseInfo) => { + .then((res: SubnetInfo) => { setLicenseInfo(res); setLoadingLicenseInfo(false); }) diff --git a/portal-ui/src/screens/Console/Tools/Tools.tsx b/portal-ui/src/screens/Console/Tools/Tools.tsx index e98aa0e47..d1bddb0cd 100644 --- a/portal-ui/src/screens/Console/Tools/Tools.tsx +++ b/portal-ui/src/screens/Console/Tools/Tools.tsx @@ -19,6 +19,7 @@ import { Route, Router, Switch } from "react-router-dom"; import history from "../../../history"; import NotFoundPage from "../../NotFoundPage"; import ToolsList from "./ToolsPanel/ToolsList"; +import Register from "../Support/Register"; import { IAM_PAGES } from "../../../common/SecureComponent/permissions"; import FeatureNotAvailablePage from "../Common/Components/FeatureNotAvailablePage"; import { SupportMenuIcon } from "../../../icons/SidebarMenus/MenuIcons"; @@ -28,20 +29,7 @@ const Tools = () => { - { - return ( - } - pageHeaderText={"Register"} - title={"Product registration"} - message={
This feature is currently not available.
} - /> - ); - }} - /> + . +// + +package restapi + +import ( + "context" + "errors" + + "github.com/go-openapi/runtime/middleware" + "github.com/minio/console/cluster" + "github.com/minio/console/models" + "github.com/minio/console/pkg/subnet" + "github.com/minio/console/restapi/operations" + "github.com/minio/console/restapi/operations/admin_api" + "github.com/minio/madmin-go" +) + +func registerSubnetHandlers(api *operations.ConsoleAPI) { + // Get subnet login handler + api.AdminAPISubnetLoginHandler = admin_api.SubnetLoginHandlerFunc(func(params admin_api.SubnetLoginParams, session *models.Principal) middleware.Responder { + resp, err := GetSubnetLoginResponse(session, params) + if err != nil { + return admin_api.NewSubnetLoginDefault(int(err.Code)).WithPayload(err) + } + return admin_api.NewSubnetLoginOK().WithPayload(resp) + }) + // Get subnet login with MFA handler + api.AdminAPISubnetLoginMFAHandler = admin_api.SubnetLoginMFAHandlerFunc(func(params admin_api.SubnetLoginMFAParams, session *models.Principal) middleware.Responder { + resp, err := GetSubnetLoginWithMFAResponse(params) + if err != nil { + return admin_api.NewSubnetLoginMFADefault(int(err.Code)).WithPayload(err) + } + return admin_api.NewSubnetLoginMFAOK().WithPayload(resp) + }) + // Get subnet register + api.AdminAPISubnetRegisterHandler = admin_api.SubnetRegisterHandlerFunc(func(params admin_api.SubnetRegisterParams, session *models.Principal) middleware.Responder { + err := GetSubnetRegisterResponse(session, params) + if err != nil { + return admin_api.NewSubnetRegisterDefault(int(err.Code)).WithPayload(err) + } + return admin_api.NewSubnetRegisterOK() + }) + // Get subnet info + api.AdminAPISubnetInfoHandler = admin_api.SubnetInfoHandlerFunc(func(params admin_api.SubnetInfoParams, session *models.Principal) middleware.Responder { + err := GetSubnetInfoResponse(session) + if err != nil { + return admin_api.NewSubnetInfoDefault(int(err.Code)).WithPayload(err) + } + return admin_api.NewSubnetInfoOK() + }) + // Get subnet registration token + api.AdminAPISubnetRegTokenHandler = admin_api.SubnetRegTokenHandlerFunc(func(params admin_api.SubnetRegTokenParams, session *models.Principal) middleware.Responder { + resp, err := GetSubnetRegTokenResponse(session) + if err != nil { + return admin_api.NewSubnetRegTokenDefault(int(err.Code)).WithPayload(err) + } + return admin_api.NewSubnetRegTokenOK().WithPayload(resp) + }) +} + +func SubnetRegisterWithAPIKey(ctx context.Context, minioClient MinioAdmin, apiKey string) (bool, error) { + serverInfo, err := minioClient.serverInfo(ctx) + if err != nil { + return false, err + } + subnetAPIKey, err := subnet.Register(httpClient, serverInfo, apiKey, "", "") + if err != nil { + return false, err + } + configStr := "subnet license= api_key=" + subnetAPIKey + _, err = minioClient.setConfigKV(ctx, configStr) + if err != nil { + return false, err + } + // cluster registered correctly + return true, nil +} + +func SubnetLogin(client cluster.HTTPClientI, username, password string) (string, string, error) { + tokens, err := subnet.Login(client, username, password) + if err != nil { + return "", "", err + } + if tokens.MfaToken != "" { + // user needs to complete login flow using mfa + return "", tokens.MfaToken, nil + } + if tokens.AccessToken != "" { + // register token to minio + return tokens.AccessToken, "", nil + } + return "", "", errors.New("something went wrong") +} + +func GetSubnetLoginResponse(session *models.Principal, params admin_api.SubnetLoginParams) (*models.SubnetLoginResponse, *models.Error) { + ctx := context.Background() + httpClient := &cluster.HTTPClient{ + Client: GetConsoleHTTPClient(), + } + mAdmin, err := NewMinioAdminClient(session) + if err != nil { + return nil, prepareError(err) + } + minioClient := AdminClient{Client: mAdmin} + apiKey := params.Body.APIKey + if apiKey != "" { + registered, err := SubnetRegisterWithAPIKey(ctx, minioClient, apiKey) + if err != nil { + return nil, prepareError(err) + } + return &models.SubnetLoginResponse{ + Registered: registered, + Organizations: []*models.SubnetOrganization{}, + }, nil + } + username := params.Body.Username + password := params.Body.Password + if username != "" && password != "" { + token, mfa, err := SubnetLogin(httpClient, username, password) + if err != nil { + return nil, prepareError(err) + } + return &models.SubnetLoginResponse{ + MfaToken: mfa, + AccessToken: token, + Organizations: []*models.SubnetOrganization{}, + }, nil + } + return nil, prepareError(ErrorGeneric) +} + +type SubnetRegistration struct { + AccessToken string + MFAToken string + Organizations []models.SubnetOrganization +} + +func SubnetLoginWithMFA(client cluster.HTTPClientI, username, mfaToken, otp string) (*models.SubnetLoginResponse, error) { + tokens, err := subnet.LoginWithMFA(client, username, mfaToken, otp) + if err != nil { + return nil, err + } + if tokens.AccessToken != "" { + organizations, errOrg := subnet.GetOrganizations(client, tokens.AccessToken) + if errOrg != nil { + return nil, errOrg + } + return &models.SubnetLoginResponse{ + AccessToken: tokens.AccessToken, + Organizations: organizations, + }, nil + } + return nil, errors.New("something went wrong") +} + +func GetSubnetLoginWithMFAResponse(params admin_api.SubnetLoginMFAParams) (*models.SubnetLoginResponse, *models.Error) { + client := &cluster.HTTPClient{ + Client: GetConsoleHTTPClient(), + } + resp, err := SubnetLoginWithMFA(client, *params.Body.Username, *params.Body.MfaToken, *params.Body.Otp) + if err != nil { + return nil, prepareError(err) + } + return resp, nil +} + +func GetSubnetKeyFromMinIOConfig(ctx context.Context, minioClient MinioAdmin, key string) (string, error) { + sh, err := minioClient.helpConfigKV(ctx, "subnet", "", false) + if err != nil { + return "", err + } + buf, err := minioClient.getConfigKV(ctx, "subnet") + if err != nil { + return "", err + } + tgt, err := madmin.ParseSubSysTarget(buf, sh) + if err != nil { + return "", err + } + + for _, kv := range tgt.KVS { + if kv.Key == key { + return kv.Value, nil + } + } + return "", errors.New("") +} + +func GetSubnetRegister(ctx context.Context, minioClient MinioAdmin, httpClient cluster.HTTPClientI, params admin_api.SubnetRegisterParams) error { + serverInfo, err := minioClient.serverInfo(ctx) + if err != nil { + return err + } + subnetAPIKey, err := subnet.Register(httpClient, serverInfo, "", *params.Body.Token, *params.Body.AccountID) + if err != nil { + return err + } + configStr := "subnet license= api_key=" + subnetAPIKey + _, err = minioClient.setConfigKV(ctx, configStr) + if err != nil { + return err + } + return nil +} + +func GetSubnetRegisterResponse(session *models.Principal, params admin_api.SubnetRegisterParams) *models.Error { + ctx := context.Background() + mAdmin, err := NewMinioAdminClient(session) + if err != nil { + return prepareError(err) + } + adminClient := AdminClient{Client: mAdmin} + client := &cluster.HTTPClient{ + Client: GetConsoleHTTPClient(), + } + err = GetSubnetRegister(ctx, adminClient, client, params) + if err != nil { + return prepareError(err) + } + return nil +} + +func GetSubnetInfoResponse(session *models.Principal) *models.Error { + ctx := context.Background() + mAdmin, err := NewMinioAdminClient(session) + if err != nil { + return prepareError(err) + } + adminClient := AdminClient{Client: mAdmin} + apiKey, err := GetSubnetKeyFromMinIOConfig(ctx, adminClient, "api_key") + if err != nil { + return prepareError(err) + } + if apiKey == "" { + return prepareError(errLicenseNotFound) + } + return nil +} + +func GetSubnetRegToken(ctx context.Context, minioClient MinioAdmin) (string, error) { + serverInfo, err := minioClient.serverInfo(ctx) + if err != nil { + return "", err + } + regInfo := subnet.GetClusterRegInfo(serverInfo) + regToken, err := subnet.GenerateRegToken(regInfo) + if err != nil { + return "", err + } + return regToken, nil +} + +func GetSubnetRegTokenResponse(session *models.Principal) (*models.SubnetRegTokenResponse, *models.Error) { + ctx := context.Background() + mAdmin, err := NewMinioAdminClient(session) + if err != nil { + return nil, prepareError(err) + } + adminClient := AdminClient{Client: mAdmin} + token, err := GetSubnetRegToken(ctx, adminClient) + if err != nil { + return nil, prepareError(err) + } + return &models.SubnetRegTokenResponse{ + RegToken: token, + }, nil +} diff --git a/restapi/admin_subscription.go b/restapi/admin_subscription.go deleted file mode 100644 index cf64a83c3..000000000 --- a/restapi/admin_subscription.go +++ /dev/null @@ -1,78 +0,0 @@ -// This file is part of MinIO Console Server -// Copyright (c) 2021 MinIO, Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . -// - -package restapi - -import ( - "github.com/minio/console/cluster" - "github.com/minio/console/pkg/subnet" - - "github.com/go-openapi/runtime/middleware" - "github.com/minio/console/models" - "github.com/minio/console/restapi/operations" - "github.com/minio/console/restapi/operations/admin_api" -) - -func registerSubscriptionHandlers(api *operations.ConsoleAPI) { - // Get subscription information handler - api.AdminAPISubscriptionInfoHandler = admin_api.SubscriptionInfoHandlerFunc(func(params admin_api.SubscriptionInfoParams, session *models.Principal) middleware.Responder { - license, err := getSubscriptionInfoResponse() - if err != nil { - return admin_api.NewSubscriptionInfoDefault(int(err.Code)).WithPayload(err) - } - return admin_api.NewSubscriptionInfoOK().WithPayload(license) - }) -} - -// retrieveLicense returns license from K8S secrets (If console is deployed in operator mode) or from -// the configured CONSOLE_SUBNET_LICENSE environment variable -func retrieveLicense() string { - // If Console is running in Tenant Admin mode retrieve license from env variable - license := GetSubnetLicense() - return license -} - -// subscriptionValidate will validate the provided jwt license against the subnet public key -func subscriptionValidate(client cluster.HTTPClientI, license, email, password string) (*models.License, string, error) { - licenseInfo, rawLicense, err := subnet.ValidateLicense(client, license, email, password) - if err != nil { - return nil, "", err - } - return &models.License{ - Email: licenseInfo.Email, - AccountID: licenseInfo.AccountID, - StorageCapacity: licenseInfo.StorageCapacity, - Plan: licenseInfo.Plan, - ExpiresAt: licenseInfo.ExpiresAt.String(), - Organization: licenseInfo.Organization, - }, rawLicense, nil -} - -// getSubscriptionInfoResponse returns information about the current configured subnet license for Console -func getSubscriptionInfoResponse() (*models.License, *models.Error) { - var licenseInfo *models.License - client := &cluster.HTTPClient{ - Client: GetConsoleHTTPClient(), - } - licenseKey := retrieveLicense() - // validate license key and obtain license info - licenseInfo, _, err := subscriptionValidate(client, licenseKey, "", "") - if err != nil { - return nil, prepareError(errLicenseNotFound, nil, err) - } - return licenseInfo, nil -} diff --git a/restapi/configure_console.go b/restapi/configure_console.go index bdcc26215..4c5e38615 100644 --- a/restapi/configure_console.go +++ b/restapi/configure_console.go @@ -118,8 +118,8 @@ func configureAPI(api *operations.ConsoleAPI) http.Handler { registerAdminBucketRemoteHandlers(api) // Register admin log search registerLogSearchHandlers(api) - // Register admin subscription handlers - registerSubscriptionHandlers(api) + // Register admin subnet handlers + registerSubnetHandlers(api) // Register Account handlers registerAdminTiersHandlers(api) diff --git a/restapi/embedded_spec.go b/restapi/embedded_spec.go index fd374e482..24141eacb 100644 --- a/restapi/embedded_spec.go +++ b/restapi/embedded_spec.go @@ -3212,13 +3212,13 @@ func init() { } } }, - "/subscription/info": { + "/subnet/info": { "get": { "tags": [ "AdminAPI" ], - "summary": "Subscription info", - "operationId": "SubscriptionInfo", + "summary": "Subnet info", + "operationId": "SubnetInfo", "responses": { "200": { "description": "A successful response.", @@ -3235,6 +3235,125 @@ func init() { } } }, + "/subnet/login": { + "post": { + "tags": [ + "AdminAPI" + ], + "summary": "Login to subnet", + "operationId": "SubnetLogin", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/subnetLoginRequest" + } + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/subnetLoginResponse" + } + }, + "default": { + "description": "Generic error response.", + "schema": { + "$ref": "#/definitions/error" + } + } + } + } + }, + "/subnet/login/mfa": { + "post": { + "tags": [ + "AdminAPI" + ], + "summary": "Login to subnet using mfa", + "operationId": "SubnetLoginMFA", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/subnetLoginMFARequest" + } + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/subnetLoginResponse" + } + }, + "default": { + "description": "Generic error response.", + "schema": { + "$ref": "#/definitions/error" + } + } + } + } + }, + "/subnet/register": { + "post": { + "tags": [ + "AdminAPI" + ], + "summary": "Register cluster with Subnet", + "operationId": "SubnetRegister", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/subnetRegisterRequest" + } + } + ], + "responses": { + "200": { + "description": "A successful response." + }, + "default": { + "description": "Generic error response.", + "schema": { + "$ref": "#/definitions/error" + } + } + } + } + }, + "/subnet/registration-token": { + "get": { + "tags": [ + "AdminAPI" + ], + "summary": "Subnet registraton token", + "operationId": "SubnetRegToken", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/SubnetRegTokenResponse" + } + }, + "default": { + "description": "Generic error response.", + "schema": { + "$ref": "#/definitions/error" + } + } + } + } + }, "/user": { "get": { "tags": [ @@ -3575,6 +3694,14 @@ func init() { } }, "definitions": { + "SubnetRegTokenResponse": { + "type": "object", + "properties": { + "regToken": { + "type": "string" + } + } + }, "accessRule": { "type": "object", "properties": { @@ -5547,6 +5674,97 @@ func init() { } } }, + "subnetLoginMFARequest": { + "type": "object", + "required": [ + "username", + "otp", + "mfa_token" + ], + "properties": { + "mfa_token": { + "type": "string" + }, + "otp": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "subnetLoginRequest": { + "type": "object", + "properties": { + "apiKey": { + "type": "string" + }, + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "subnetLoginResponse": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "mfa_token": { + "type": "string" + }, + "organizations": { + "type": "array", + "items": { + "$ref": "#/definitions/subnetOrganization" + } + }, + "registered": { + "type": "boolean" + } + } + }, + "subnetOrganization": { + "type": "object", + "properties": { + "accountId": { + "type": "integer" + }, + "company": { + "type": "string" + }, + "isAccountOwner": { + "type": "boolean" + }, + "shortName": { + "type": "string" + }, + "subscriptionStatus": { + "type": "string" + }, + "userId": { + "type": "integer" + } + } + }, + "subnetRegisterRequest": { + "type": "object", + "required": [ + "token", + "account_id" + ], + "properties": { + "account_id": { + "type": "string" + }, + "token": { + "type": "string" + } + } + }, "tier": { "type": "object", "properties": { @@ -9061,13 +9279,13 @@ func init() { } } }, - "/subscription/info": { + "/subnet/info": { "get": { "tags": [ "AdminAPI" ], - "summary": "Subscription info", - "operationId": "SubscriptionInfo", + "summary": "Subnet info", + "operationId": "SubnetInfo", "responses": { "200": { "description": "A successful response.", @@ -9084,6 +9302,125 @@ func init() { } } }, + "/subnet/login": { + "post": { + "tags": [ + "AdminAPI" + ], + "summary": "Login to subnet", + "operationId": "SubnetLogin", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/subnetLoginRequest" + } + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/subnetLoginResponse" + } + }, + "default": { + "description": "Generic error response.", + "schema": { + "$ref": "#/definitions/error" + } + } + } + } + }, + "/subnet/login/mfa": { + "post": { + "tags": [ + "AdminAPI" + ], + "summary": "Login to subnet using mfa", + "operationId": "SubnetLoginMFA", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/subnetLoginMFARequest" + } + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/subnetLoginResponse" + } + }, + "default": { + "description": "Generic error response.", + "schema": { + "$ref": "#/definitions/error" + } + } + } + } + }, + "/subnet/register": { + "post": { + "tags": [ + "AdminAPI" + ], + "summary": "Register cluster with Subnet", + "operationId": "SubnetRegister", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/subnetRegisterRequest" + } + } + ], + "responses": { + "200": { + "description": "A successful response." + }, + "default": { + "description": "Generic error response.", + "schema": { + "$ref": "#/definitions/error" + } + } + } + } + }, + "/subnet/registration-token": { + "get": { + "tags": [ + "AdminAPI" + ], + "summary": "Subnet registraton token", + "operationId": "SubnetRegToken", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/SubnetRegTokenResponse" + } + }, + "default": { + "description": "Generic error response.", + "schema": { + "$ref": "#/definitions/error" + } + } + } + } + }, "/user": { "get": { "tags": [ @@ -9490,6 +9827,14 @@ func init() { } } }, + "SubnetRegTokenResponse": { + "type": "object", + "properties": { + "regToken": { + "type": "string" + } + } + }, "WidgetDetailsOptions": { "type": "object", "properties": { @@ -11516,6 +11861,97 @@ func init() { } } }, + "subnetLoginMFARequest": { + "type": "object", + "required": [ + "username", + "otp", + "mfa_token" + ], + "properties": { + "mfa_token": { + "type": "string" + }, + "otp": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "subnetLoginRequest": { + "type": "object", + "properties": { + "apiKey": { + "type": "string" + }, + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "subnetLoginResponse": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "mfa_token": { + "type": "string" + }, + "organizations": { + "type": "array", + "items": { + "$ref": "#/definitions/subnetOrganization" + } + }, + "registered": { + "type": "boolean" + } + } + }, + "subnetOrganization": { + "type": "object", + "properties": { + "accountId": { + "type": "integer" + }, + "company": { + "type": "string" + }, + "isAccountOwner": { + "type": "boolean" + }, + "shortName": { + "type": "string" + }, + "subscriptionStatus": { + "type": "string" + }, + "userId": { + "type": "integer" + } + } + }, + "subnetRegisterRequest": { + "type": "object", + "required": [ + "token", + "account_id" + ], + "properties": { + "account_id": { + "type": "string" + }, + "token": { + "type": "string" + } + } + }, "tier": { "type": "object", "properties": { diff --git a/restapi/operations/admin_api/subscription_info.go b/restapi/operations/admin_api/subnet_info.go similarity index 63% rename from restapi/operations/admin_api/subscription_info.go rename to restapi/operations/admin_api/subnet_info.go index 923161f74..9e0587a5d 100644 --- a/restapi/operations/admin_api/subscription_info.go +++ b/restapi/operations/admin_api/subnet_info.go @@ -30,40 +30,40 @@ import ( "github.com/minio/console/models" ) -// SubscriptionInfoHandlerFunc turns a function with the right signature into a subscription info handler -type SubscriptionInfoHandlerFunc func(SubscriptionInfoParams, *models.Principal) middleware.Responder +// SubnetInfoHandlerFunc turns a function with the right signature into a subnet info handler +type SubnetInfoHandlerFunc func(SubnetInfoParams, *models.Principal) middleware.Responder // Handle executing the request and returning a response -func (fn SubscriptionInfoHandlerFunc) Handle(params SubscriptionInfoParams, principal *models.Principal) middleware.Responder { +func (fn SubnetInfoHandlerFunc) Handle(params SubnetInfoParams, principal *models.Principal) middleware.Responder { return fn(params, principal) } -// SubscriptionInfoHandler interface for that can handle valid subscription info params -type SubscriptionInfoHandler interface { - Handle(SubscriptionInfoParams, *models.Principal) middleware.Responder +// SubnetInfoHandler interface for that can handle valid subnet info params +type SubnetInfoHandler interface { + Handle(SubnetInfoParams, *models.Principal) middleware.Responder } -// NewSubscriptionInfo creates a new http.Handler for the subscription info operation -func NewSubscriptionInfo(ctx *middleware.Context, handler SubscriptionInfoHandler) *SubscriptionInfo { - return &SubscriptionInfo{Context: ctx, Handler: handler} +// NewSubnetInfo creates a new http.Handler for the subnet info operation +func NewSubnetInfo(ctx *middleware.Context, handler SubnetInfoHandler) *SubnetInfo { + return &SubnetInfo{Context: ctx, Handler: handler} } -/* SubscriptionInfo swagger:route GET /subscription/info AdminAPI subscriptionInfo +/* SubnetInfo swagger:route GET /subnet/info AdminAPI subnetInfo -Subscription info +Subnet info */ -type SubscriptionInfo struct { +type SubnetInfo struct { Context *middleware.Context - Handler SubscriptionInfoHandler + Handler SubnetInfoHandler } -func (o *SubscriptionInfo) ServeHTTP(rw http.ResponseWriter, r *http.Request) { +func (o *SubnetInfo) ServeHTTP(rw http.ResponseWriter, r *http.Request) { route, rCtx, _ := o.Context.RouteInfo(r) if rCtx != nil { *r = *rCtx } - var Params = NewSubscriptionInfoParams() + var Params = NewSubnetInfoParams() uprinc, aCtx, err := o.Context.Authorize(r, route) if err != nil { o.Context.Respond(rw, r, route.Produces, route, err) diff --git a/restapi/operations/admin_api/subscription_info_parameters.go b/restapi/operations/admin_api/subnet_info_parameters.go similarity index 77% rename from restapi/operations/admin_api/subscription_info_parameters.go rename to restapi/operations/admin_api/subnet_info_parameters.go index 042842026..4a53973e6 100644 --- a/restapi/operations/admin_api/subscription_info_parameters.go +++ b/restapi/operations/admin_api/subnet_info_parameters.go @@ -29,19 +29,19 @@ import ( "github.com/go-openapi/runtime/middleware" ) -// NewSubscriptionInfoParams creates a new SubscriptionInfoParams object +// NewSubnetInfoParams creates a new SubnetInfoParams object // // There are no default values defined in the spec. -func NewSubscriptionInfoParams() SubscriptionInfoParams { +func NewSubnetInfoParams() SubnetInfoParams { - return SubscriptionInfoParams{} + return SubnetInfoParams{} } -// SubscriptionInfoParams contains all the bound params for the subscription info operation +// SubnetInfoParams contains all the bound params for the subnet info operation // typically these are obtained from a http.Request // -// swagger:parameters SubscriptionInfo -type SubscriptionInfoParams struct { +// swagger:parameters SubnetInfo +type SubnetInfoParams struct { // HTTP Request Object HTTPRequest *http.Request `json:"-"` @@ -50,8 +50,8 @@ type SubscriptionInfoParams struct { // 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 NewSubscriptionInfoParams() beforehand. -func (o *SubscriptionInfoParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { +// To ensure default values, the struct must have been initialized with NewSubnetInfoParams() beforehand. +func (o *SubnetInfoParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { var res []error o.HTTPRequest = r diff --git a/restapi/operations/admin_api/subscription_info_responses.go b/restapi/operations/admin_api/subnet_info_responses.go similarity index 51% rename from restapi/operations/admin_api/subscription_info_responses.go rename to restapi/operations/admin_api/subnet_info_responses.go index 3533092d9..76ba9dc31 100644 --- a/restapi/operations/admin_api/subscription_info_responses.go +++ b/restapi/operations/admin_api/subnet_info_responses.go @@ -30,14 +30,14 @@ import ( "github.com/minio/console/models" ) -// SubscriptionInfoOKCode is the HTTP code returned for type SubscriptionInfoOK -const SubscriptionInfoOKCode int = 200 +// SubnetInfoOKCode is the HTTP code returned for type SubnetInfoOK +const SubnetInfoOKCode int = 200 -/*SubscriptionInfoOK A successful response. +/*SubnetInfoOK A successful response. -swagger:response subscriptionInfoOK +swagger:response subnetInfoOK */ -type SubscriptionInfoOK struct { +type SubnetInfoOK struct { /* In: Body @@ -45,25 +45,25 @@ type SubscriptionInfoOK struct { Payload *models.License `json:"body,omitempty"` } -// NewSubscriptionInfoOK creates SubscriptionInfoOK with default headers values -func NewSubscriptionInfoOK() *SubscriptionInfoOK { +// NewSubnetInfoOK creates SubnetInfoOK with default headers values +func NewSubnetInfoOK() *SubnetInfoOK { - return &SubscriptionInfoOK{} + return &SubnetInfoOK{} } -// WithPayload adds the payload to the subscription info o k response -func (o *SubscriptionInfoOK) WithPayload(payload *models.License) *SubscriptionInfoOK { +// WithPayload adds the payload to the subnet info o k response +func (o *SubnetInfoOK) WithPayload(payload *models.License) *SubnetInfoOK { o.Payload = payload return o } -// SetPayload sets the payload to the subscription info o k response -func (o *SubscriptionInfoOK) SetPayload(payload *models.License) { +// SetPayload sets the payload to the subnet info o k response +func (o *SubnetInfoOK) SetPayload(payload *models.License) { o.Payload = payload } // WriteResponse to the client -func (o *SubscriptionInfoOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { +func (o *SubnetInfoOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { rw.WriteHeader(200) if o.Payload != nil { @@ -74,11 +74,11 @@ func (o *SubscriptionInfoOK) WriteResponse(rw http.ResponseWriter, producer runt } } -/*SubscriptionInfoDefault Generic error response. +/*SubnetInfoDefault Generic error response. -swagger:response subscriptionInfoDefault +swagger:response subnetInfoDefault */ -type SubscriptionInfoDefault struct { +type SubnetInfoDefault struct { _statusCode int /* @@ -87,41 +87,41 @@ type SubscriptionInfoDefault struct { Payload *models.Error `json:"body,omitempty"` } -// NewSubscriptionInfoDefault creates SubscriptionInfoDefault with default headers values -func NewSubscriptionInfoDefault(code int) *SubscriptionInfoDefault { +// NewSubnetInfoDefault creates SubnetInfoDefault with default headers values +func NewSubnetInfoDefault(code int) *SubnetInfoDefault { if code <= 0 { code = 500 } - return &SubscriptionInfoDefault{ + return &SubnetInfoDefault{ _statusCode: code, } } -// WithStatusCode adds the status to the subscription info default response -func (o *SubscriptionInfoDefault) WithStatusCode(code int) *SubscriptionInfoDefault { +// WithStatusCode adds the status to the subnet info default response +func (o *SubnetInfoDefault) WithStatusCode(code int) *SubnetInfoDefault { o._statusCode = code return o } -// SetStatusCode sets the status to the subscription info default response -func (o *SubscriptionInfoDefault) SetStatusCode(code int) { +// SetStatusCode sets the status to the subnet info default response +func (o *SubnetInfoDefault) SetStatusCode(code int) { o._statusCode = code } -// WithPayload adds the payload to the subscription info default response -func (o *SubscriptionInfoDefault) WithPayload(payload *models.Error) *SubscriptionInfoDefault { +// WithPayload adds the payload to the subnet info default response +func (o *SubnetInfoDefault) WithPayload(payload *models.Error) *SubnetInfoDefault { o.Payload = payload return o } -// SetPayload sets the payload to the subscription info default response -func (o *SubscriptionInfoDefault) SetPayload(payload *models.Error) { +// SetPayload sets the payload to the subnet info default response +func (o *SubnetInfoDefault) SetPayload(payload *models.Error) { o.Payload = payload } // WriteResponse to the client -func (o *SubscriptionInfoDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { +func (o *SubnetInfoDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { rw.WriteHeader(o._statusCode) if o.Payload != nil { diff --git a/restapi/operations/admin_api/subscription_info_urlbuilder.go b/restapi/operations/admin_api/subnet_info_urlbuilder.go similarity index 79% rename from restapi/operations/admin_api/subscription_info_urlbuilder.go rename to restapi/operations/admin_api/subnet_info_urlbuilder.go index 5fa9736f5..ab8e63b93 100644 --- a/restapi/operations/admin_api/subscription_info_urlbuilder.go +++ b/restapi/operations/admin_api/subnet_info_urlbuilder.go @@ -28,15 +28,15 @@ import ( golangswaggerpaths "path" ) -// SubscriptionInfoURL generates an URL for the subscription info operation -type SubscriptionInfoURL struct { +// SubnetInfoURL generates an URL for the subnet info operation +type SubnetInfoURL 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 *SubscriptionInfoURL) WithBasePath(bp string) *SubscriptionInfoURL { +func (o *SubnetInfoURL) WithBasePath(bp string) *SubnetInfoURL { o.SetBasePath(bp) return o } @@ -44,15 +44,15 @@ func (o *SubscriptionInfoURL) WithBasePath(bp string) *SubscriptionInfoURL { // 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 *SubscriptionInfoURL) SetBasePath(bp string) { +func (o *SubnetInfoURL) SetBasePath(bp string) { o._basePath = bp } // Build a url path and query string -func (o *SubscriptionInfoURL) Build() (*url.URL, error) { +func (o *SubnetInfoURL) Build() (*url.URL, error) { var _result url.URL - var _path = "/subscription/info" + var _path = "/subnet/info" _basePath := o._basePath if _basePath == "" { @@ -64,7 +64,7 @@ func (o *SubscriptionInfoURL) Build() (*url.URL, error) { } // Must is a helper function to panic when the url builder returns an error -func (o *SubscriptionInfoURL) Must(u *url.URL, err error) *url.URL { +func (o *SubnetInfoURL) Must(u *url.URL, err error) *url.URL { if err != nil { panic(err) } @@ -75,17 +75,17 @@ func (o *SubscriptionInfoURL) Must(u *url.URL, err error) *url.URL { } // String returns the string representation of the path with query string -func (o *SubscriptionInfoURL) String() string { +func (o *SubnetInfoURL) String() string { return o.Must(o.Build()).String() } // BuildFull builds a full url with scheme, host, path and query string -func (o *SubscriptionInfoURL) BuildFull(scheme, host string) (*url.URL, error) { +func (o *SubnetInfoURL) BuildFull(scheme, host string) (*url.URL, error) { if scheme == "" { - return nil, errors.New("scheme is required for a full url on SubscriptionInfoURL") + return nil, errors.New("scheme is required for a full url on SubnetInfoURL") } if host == "" { - return nil, errors.New("host is required for a full url on SubscriptionInfoURL") + return nil, errors.New("host is required for a full url on SubnetInfoURL") } base, err := o.Build() @@ -99,6 +99,6 @@ func (o *SubscriptionInfoURL) BuildFull(scheme, host string) (*url.URL, error) { } // StringFull returns the string representation of a complete url -func (o *SubscriptionInfoURL) StringFull(scheme, host string) string { +func (o *SubnetInfoURL) StringFull(scheme, host string) string { return o.Must(o.BuildFull(scheme, host)).String() } diff --git a/restapi/operations/admin_api/subnet_login.go b/restapi/operations/admin_api/subnet_login.go new file mode 100644 index 000000000..c51d9de87 --- /dev/null +++ b/restapi/operations/admin_api/subnet_login.go @@ -0,0 +1,88 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// + +package admin_api + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" + + "github.com/minio/console/models" +) + +// SubnetLoginHandlerFunc turns a function with the right signature into a subnet login handler +type SubnetLoginHandlerFunc func(SubnetLoginParams, *models.Principal) middleware.Responder + +// Handle executing the request and returning a response +func (fn SubnetLoginHandlerFunc) Handle(params SubnetLoginParams, principal *models.Principal) middleware.Responder { + return fn(params, principal) +} + +// SubnetLoginHandler interface for that can handle valid subnet login params +type SubnetLoginHandler interface { + Handle(SubnetLoginParams, *models.Principal) middleware.Responder +} + +// NewSubnetLogin creates a new http.Handler for the subnet login operation +func NewSubnetLogin(ctx *middleware.Context, handler SubnetLoginHandler) *SubnetLogin { + return &SubnetLogin{Context: ctx, Handler: handler} +} + +/* SubnetLogin swagger:route POST /subnet/login AdminAPI subnetLogin + +Login to subnet + +*/ +type SubnetLogin struct { + Context *middleware.Context + Handler SubnetLoginHandler +} + +func (o *SubnetLogin) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewSubnetLoginParams() + 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 *models.Principal + if uprinc != nil { + principal = uprinc.(*models.Principal) // this is really a models.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) + +} diff --git a/restapi/operations/admin_api/subnet_login_m_f_a.go b/restapi/operations/admin_api/subnet_login_m_f_a.go new file mode 100644 index 000000000..9fcb9d83a --- /dev/null +++ b/restapi/operations/admin_api/subnet_login_m_f_a.go @@ -0,0 +1,88 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// + +package admin_api + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" + + "github.com/minio/console/models" +) + +// SubnetLoginMFAHandlerFunc turns a function with the right signature into a subnet login m f a handler +type SubnetLoginMFAHandlerFunc func(SubnetLoginMFAParams, *models.Principal) middleware.Responder + +// Handle executing the request and returning a response +func (fn SubnetLoginMFAHandlerFunc) Handle(params SubnetLoginMFAParams, principal *models.Principal) middleware.Responder { + return fn(params, principal) +} + +// SubnetLoginMFAHandler interface for that can handle valid subnet login m f a params +type SubnetLoginMFAHandler interface { + Handle(SubnetLoginMFAParams, *models.Principal) middleware.Responder +} + +// NewSubnetLoginMFA creates a new http.Handler for the subnet login m f a operation +func NewSubnetLoginMFA(ctx *middleware.Context, handler SubnetLoginMFAHandler) *SubnetLoginMFA { + return &SubnetLoginMFA{Context: ctx, Handler: handler} +} + +/* SubnetLoginMFA swagger:route POST /subnet/login/mfa AdminAPI subnetLoginMFA + +Login to subnet using mfa + +*/ +type SubnetLoginMFA struct { + Context *middleware.Context + Handler SubnetLoginMFAHandler +} + +func (o *SubnetLoginMFA) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewSubnetLoginMFAParams() + 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 *models.Principal + if uprinc != nil { + principal = uprinc.(*models.Principal) // this is really a models.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) + +} diff --git a/restapi/operations/admin_api/subnet_login_m_f_a_parameters.go b/restapi/operations/admin_api/subnet_login_m_f_a_parameters.go new file mode 100644 index 000000000..d01e7539d --- /dev/null +++ b/restapi/operations/admin_api/subnet_login_m_f_a_parameters.go @@ -0,0 +1,102 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// + +package admin_api + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "io" + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/validate" + + "github.com/minio/console/models" +) + +// NewSubnetLoginMFAParams creates a new SubnetLoginMFAParams object +// +// There are no default values defined in the spec. +func NewSubnetLoginMFAParams() SubnetLoginMFAParams { + + return SubnetLoginMFAParams{} +} + +// SubnetLoginMFAParams contains all the bound params for the subnet login m f a operation +// typically these are obtained from a http.Request +// +// swagger:parameters SubnetLoginMFA +type SubnetLoginMFAParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + Required: true + In: body + */ + Body *models.SubnetLoginMFARequest +} + +// 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 NewSubnetLoginMFAParams() beforehand. +func (o *SubnetLoginMFAParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + if runtime.HasBody(r) { + defer r.Body.Close() + var body models.SubnetLoginMFARequest + if err := route.Consumer.Consume(r.Body, &body); err != nil { + if err == io.EOF { + res = append(res, errors.Required("body", "body", "")) + } else { + 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(context.Background()) + if err := body.ContextValidate(ctx, route.Formats); err != nil { + res = append(res, err) + } + + if len(res) == 0 { + o.Body = &body + } + } + } else { + res = append(res, errors.Required("body", "body", "")) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/restapi/operations/admin_api/subnet_login_m_f_a_responses.go b/restapi/operations/admin_api/subnet_login_m_f_a_responses.go new file mode 100644 index 000000000..46230c0f9 --- /dev/null +++ b/restapi/operations/admin_api/subnet_login_m_f_a_responses.go @@ -0,0 +1,133 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// + +package admin_api + +// 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" + + "github.com/minio/console/models" +) + +// SubnetLoginMFAOKCode is the HTTP code returned for type SubnetLoginMFAOK +const SubnetLoginMFAOKCode int = 200 + +/*SubnetLoginMFAOK A successful response. + +swagger:response subnetLoginMFAOK +*/ +type SubnetLoginMFAOK struct { + + /* + In: Body + */ + Payload *models.SubnetLoginResponse `json:"body,omitempty"` +} + +// NewSubnetLoginMFAOK creates SubnetLoginMFAOK with default headers values +func NewSubnetLoginMFAOK() *SubnetLoginMFAOK { + + return &SubnetLoginMFAOK{} +} + +// WithPayload adds the payload to the subnet login m f a o k response +func (o *SubnetLoginMFAOK) WithPayload(payload *models.SubnetLoginResponse) *SubnetLoginMFAOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the subnet login m f a o k response +func (o *SubnetLoginMFAOK) SetPayload(payload *models.SubnetLoginResponse) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *SubnetLoginMFAOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +/*SubnetLoginMFADefault Generic error response. + +swagger:response subnetLoginMFADefault +*/ +type SubnetLoginMFADefault struct { + _statusCode int + + /* + In: Body + */ + Payload *models.Error `json:"body,omitempty"` +} + +// NewSubnetLoginMFADefault creates SubnetLoginMFADefault with default headers values +func NewSubnetLoginMFADefault(code int) *SubnetLoginMFADefault { + if code <= 0 { + code = 500 + } + + return &SubnetLoginMFADefault{ + _statusCode: code, + } +} + +// WithStatusCode adds the status to the subnet login m f a default response +func (o *SubnetLoginMFADefault) WithStatusCode(code int) *SubnetLoginMFADefault { + o._statusCode = code + return o +} + +// SetStatusCode sets the status to the subnet login m f a default response +func (o *SubnetLoginMFADefault) SetStatusCode(code int) { + o._statusCode = code +} + +// WithPayload adds the payload to the subnet login m f a default response +func (o *SubnetLoginMFADefault) WithPayload(payload *models.Error) *SubnetLoginMFADefault { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the subnet login m f a default response +func (o *SubnetLoginMFADefault) SetPayload(payload *models.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *SubnetLoginMFADefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(o._statusCode) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/restapi/operations/admin_api/subnet_login_m_f_a_urlbuilder.go b/restapi/operations/admin_api/subnet_login_m_f_a_urlbuilder.go new file mode 100644 index 000000000..3b360df02 --- /dev/null +++ b/restapi/operations/admin_api/subnet_login_m_f_a_urlbuilder.go @@ -0,0 +1,104 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// + +package admin_api + +// 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" +) + +// SubnetLoginMFAURL generates an URL for the subnet login m f a operation +type SubnetLoginMFAURL 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 *SubnetLoginMFAURL) WithBasePath(bp string) *SubnetLoginMFAURL { + 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 *SubnetLoginMFAURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *SubnetLoginMFAURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/subnet/login/mfa" + + _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 *SubnetLoginMFAURL) 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 *SubnetLoginMFAURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *SubnetLoginMFAURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on SubnetLoginMFAURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on SubnetLoginMFAURL") + } + + 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 *SubnetLoginMFAURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/restapi/operations/admin_api/subnet_login_parameters.go b/restapi/operations/admin_api/subnet_login_parameters.go new file mode 100644 index 000000000..acab3c167 --- /dev/null +++ b/restapi/operations/admin_api/subnet_login_parameters.go @@ -0,0 +1,102 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// + +package admin_api + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "io" + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/validate" + + "github.com/minio/console/models" +) + +// NewSubnetLoginParams creates a new SubnetLoginParams object +// +// There are no default values defined in the spec. +func NewSubnetLoginParams() SubnetLoginParams { + + return SubnetLoginParams{} +} + +// SubnetLoginParams contains all the bound params for the subnet login operation +// typically these are obtained from a http.Request +// +// swagger:parameters SubnetLogin +type SubnetLoginParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + Required: true + In: body + */ + Body *models.SubnetLoginRequest +} + +// 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 NewSubnetLoginParams() beforehand. +func (o *SubnetLoginParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + if runtime.HasBody(r) { + defer r.Body.Close() + var body models.SubnetLoginRequest + if err := route.Consumer.Consume(r.Body, &body); err != nil { + if err == io.EOF { + res = append(res, errors.Required("body", "body", "")) + } else { + 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(context.Background()) + if err := body.ContextValidate(ctx, route.Formats); err != nil { + res = append(res, err) + } + + if len(res) == 0 { + o.Body = &body + } + } + } else { + res = append(res, errors.Required("body", "body", "")) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/restapi/operations/admin_api/subnet_login_responses.go b/restapi/operations/admin_api/subnet_login_responses.go new file mode 100644 index 000000000..3a21e1a0d --- /dev/null +++ b/restapi/operations/admin_api/subnet_login_responses.go @@ -0,0 +1,133 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// + +package admin_api + +// 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" + + "github.com/minio/console/models" +) + +// SubnetLoginOKCode is the HTTP code returned for type SubnetLoginOK +const SubnetLoginOKCode int = 200 + +/*SubnetLoginOK A successful response. + +swagger:response subnetLoginOK +*/ +type SubnetLoginOK struct { + + /* + In: Body + */ + Payload *models.SubnetLoginResponse `json:"body,omitempty"` +} + +// NewSubnetLoginOK creates SubnetLoginOK with default headers values +func NewSubnetLoginOK() *SubnetLoginOK { + + return &SubnetLoginOK{} +} + +// WithPayload adds the payload to the subnet login o k response +func (o *SubnetLoginOK) WithPayload(payload *models.SubnetLoginResponse) *SubnetLoginOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the subnet login o k response +func (o *SubnetLoginOK) SetPayload(payload *models.SubnetLoginResponse) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *SubnetLoginOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +/*SubnetLoginDefault Generic error response. + +swagger:response subnetLoginDefault +*/ +type SubnetLoginDefault struct { + _statusCode int + + /* + In: Body + */ + Payload *models.Error `json:"body,omitempty"` +} + +// NewSubnetLoginDefault creates SubnetLoginDefault with default headers values +func NewSubnetLoginDefault(code int) *SubnetLoginDefault { + if code <= 0 { + code = 500 + } + + return &SubnetLoginDefault{ + _statusCode: code, + } +} + +// WithStatusCode adds the status to the subnet login default response +func (o *SubnetLoginDefault) WithStatusCode(code int) *SubnetLoginDefault { + o._statusCode = code + return o +} + +// SetStatusCode sets the status to the subnet login default response +func (o *SubnetLoginDefault) SetStatusCode(code int) { + o._statusCode = code +} + +// WithPayload adds the payload to the subnet login default response +func (o *SubnetLoginDefault) WithPayload(payload *models.Error) *SubnetLoginDefault { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the subnet login default response +func (o *SubnetLoginDefault) SetPayload(payload *models.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *SubnetLoginDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(o._statusCode) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/restapi/operations/admin_api/subnet_login_urlbuilder.go b/restapi/operations/admin_api/subnet_login_urlbuilder.go new file mode 100644 index 000000000..857502b1c --- /dev/null +++ b/restapi/operations/admin_api/subnet_login_urlbuilder.go @@ -0,0 +1,104 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// + +package admin_api + +// 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" +) + +// SubnetLoginURL generates an URL for the subnet login operation +type SubnetLoginURL 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 *SubnetLoginURL) WithBasePath(bp string) *SubnetLoginURL { + 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 *SubnetLoginURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *SubnetLoginURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/subnet/login" + + _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 *SubnetLoginURL) 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 *SubnetLoginURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *SubnetLoginURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on SubnetLoginURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on SubnetLoginURL") + } + + 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 *SubnetLoginURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/restapi/operations/admin_api/subnet_reg_token.go b/restapi/operations/admin_api/subnet_reg_token.go new file mode 100644 index 000000000..232c6c7ac --- /dev/null +++ b/restapi/operations/admin_api/subnet_reg_token.go @@ -0,0 +1,88 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// + +package admin_api + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" + + "github.com/minio/console/models" +) + +// SubnetRegTokenHandlerFunc turns a function with the right signature into a subnet reg token handler +type SubnetRegTokenHandlerFunc func(SubnetRegTokenParams, *models.Principal) middleware.Responder + +// Handle executing the request and returning a response +func (fn SubnetRegTokenHandlerFunc) Handle(params SubnetRegTokenParams, principal *models.Principal) middleware.Responder { + return fn(params, principal) +} + +// SubnetRegTokenHandler interface for that can handle valid subnet reg token params +type SubnetRegTokenHandler interface { + Handle(SubnetRegTokenParams, *models.Principal) middleware.Responder +} + +// NewSubnetRegToken creates a new http.Handler for the subnet reg token operation +func NewSubnetRegToken(ctx *middleware.Context, handler SubnetRegTokenHandler) *SubnetRegToken { + return &SubnetRegToken{Context: ctx, Handler: handler} +} + +/* SubnetRegToken swagger:route GET /subnet/registration-token AdminAPI subnetRegToken + +Subnet registraton token + +*/ +type SubnetRegToken struct { + Context *middleware.Context + Handler SubnetRegTokenHandler +} + +func (o *SubnetRegToken) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewSubnetRegTokenParams() + 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 *models.Principal + if uprinc != nil { + principal = uprinc.(*models.Principal) // this is really a models.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) + +} diff --git a/restapi/operations/admin_api/subnet_reg_token_parameters.go b/restapi/operations/admin_api/subnet_reg_token_parameters.go new file mode 100644 index 000000000..320d8f3c3 --- /dev/null +++ b/restapi/operations/admin_api/subnet_reg_token_parameters.go @@ -0,0 +1,63 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// + +package admin_api + +// 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/middleware" +) + +// NewSubnetRegTokenParams creates a new SubnetRegTokenParams object +// +// There are no default values defined in the spec. +func NewSubnetRegTokenParams() SubnetRegTokenParams { + + return SubnetRegTokenParams{} +} + +// SubnetRegTokenParams contains all the bound params for the subnet reg token operation +// typically these are obtained from a http.Request +// +// swagger:parameters SubnetRegToken +type SubnetRegTokenParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` +} + +// 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 NewSubnetRegTokenParams() beforehand. +func (o *SubnetRegTokenParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/restapi/operations/admin_api/subnet_reg_token_responses.go b/restapi/operations/admin_api/subnet_reg_token_responses.go new file mode 100644 index 000000000..ab7f40110 --- /dev/null +++ b/restapi/operations/admin_api/subnet_reg_token_responses.go @@ -0,0 +1,133 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// + +package admin_api + +// 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" + + "github.com/minio/console/models" +) + +// SubnetRegTokenOKCode is the HTTP code returned for type SubnetRegTokenOK +const SubnetRegTokenOKCode int = 200 + +/*SubnetRegTokenOK A successful response. + +swagger:response subnetRegTokenOK +*/ +type SubnetRegTokenOK struct { + + /* + In: Body + */ + Payload *models.SubnetRegTokenResponse `json:"body,omitempty"` +} + +// NewSubnetRegTokenOK creates SubnetRegTokenOK with default headers values +func NewSubnetRegTokenOK() *SubnetRegTokenOK { + + return &SubnetRegTokenOK{} +} + +// WithPayload adds the payload to the subnet reg token o k response +func (o *SubnetRegTokenOK) WithPayload(payload *models.SubnetRegTokenResponse) *SubnetRegTokenOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the subnet reg token o k response +func (o *SubnetRegTokenOK) SetPayload(payload *models.SubnetRegTokenResponse) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *SubnetRegTokenOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +/*SubnetRegTokenDefault Generic error response. + +swagger:response subnetRegTokenDefault +*/ +type SubnetRegTokenDefault struct { + _statusCode int + + /* + In: Body + */ + Payload *models.Error `json:"body,omitempty"` +} + +// NewSubnetRegTokenDefault creates SubnetRegTokenDefault with default headers values +func NewSubnetRegTokenDefault(code int) *SubnetRegTokenDefault { + if code <= 0 { + code = 500 + } + + return &SubnetRegTokenDefault{ + _statusCode: code, + } +} + +// WithStatusCode adds the status to the subnet reg token default response +func (o *SubnetRegTokenDefault) WithStatusCode(code int) *SubnetRegTokenDefault { + o._statusCode = code + return o +} + +// SetStatusCode sets the status to the subnet reg token default response +func (o *SubnetRegTokenDefault) SetStatusCode(code int) { + o._statusCode = code +} + +// WithPayload adds the payload to the subnet reg token default response +func (o *SubnetRegTokenDefault) WithPayload(payload *models.Error) *SubnetRegTokenDefault { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the subnet reg token default response +func (o *SubnetRegTokenDefault) SetPayload(payload *models.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *SubnetRegTokenDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(o._statusCode) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/restapi/operations/admin_api/subnet_reg_token_urlbuilder.go b/restapi/operations/admin_api/subnet_reg_token_urlbuilder.go new file mode 100644 index 000000000..65adaef68 --- /dev/null +++ b/restapi/operations/admin_api/subnet_reg_token_urlbuilder.go @@ -0,0 +1,104 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// + +package admin_api + +// 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" +) + +// SubnetRegTokenURL generates an URL for the subnet reg token operation +type SubnetRegTokenURL 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 *SubnetRegTokenURL) WithBasePath(bp string) *SubnetRegTokenURL { + 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 *SubnetRegTokenURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *SubnetRegTokenURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/subnet/registration-token" + + _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 *SubnetRegTokenURL) 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 *SubnetRegTokenURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *SubnetRegTokenURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on SubnetRegTokenURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on SubnetRegTokenURL") + } + + 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 *SubnetRegTokenURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/restapi/operations/admin_api/subnet_register.go b/restapi/operations/admin_api/subnet_register.go new file mode 100644 index 000000000..eef909691 --- /dev/null +++ b/restapi/operations/admin_api/subnet_register.go @@ -0,0 +1,88 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// + +package admin_api + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" + + "github.com/minio/console/models" +) + +// SubnetRegisterHandlerFunc turns a function with the right signature into a subnet register handler +type SubnetRegisterHandlerFunc func(SubnetRegisterParams, *models.Principal) middleware.Responder + +// Handle executing the request and returning a response +func (fn SubnetRegisterHandlerFunc) Handle(params SubnetRegisterParams, principal *models.Principal) middleware.Responder { + return fn(params, principal) +} + +// SubnetRegisterHandler interface for that can handle valid subnet register params +type SubnetRegisterHandler interface { + Handle(SubnetRegisterParams, *models.Principal) middleware.Responder +} + +// NewSubnetRegister creates a new http.Handler for the subnet register operation +func NewSubnetRegister(ctx *middleware.Context, handler SubnetRegisterHandler) *SubnetRegister { + return &SubnetRegister{Context: ctx, Handler: handler} +} + +/* SubnetRegister swagger:route POST /subnet/register AdminAPI subnetRegister + +Register cluster with Subnet + +*/ +type SubnetRegister struct { + Context *middleware.Context + Handler SubnetRegisterHandler +} + +func (o *SubnetRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewSubnetRegisterParams() + 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 *models.Principal + if uprinc != nil { + principal = uprinc.(*models.Principal) // this is really a models.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) + +} diff --git a/restapi/operations/admin_api/subnet_register_parameters.go b/restapi/operations/admin_api/subnet_register_parameters.go new file mode 100644 index 000000000..89b586c11 --- /dev/null +++ b/restapi/operations/admin_api/subnet_register_parameters.go @@ -0,0 +1,102 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// + +package admin_api + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "io" + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/validate" + + "github.com/minio/console/models" +) + +// NewSubnetRegisterParams creates a new SubnetRegisterParams object +// +// There are no default values defined in the spec. +func NewSubnetRegisterParams() SubnetRegisterParams { + + return SubnetRegisterParams{} +} + +// SubnetRegisterParams contains all the bound params for the subnet register operation +// typically these are obtained from a http.Request +// +// swagger:parameters SubnetRegister +type SubnetRegisterParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + Required: true + In: body + */ + Body *models.SubnetRegisterRequest +} + +// 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 NewSubnetRegisterParams() beforehand. +func (o *SubnetRegisterParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + if runtime.HasBody(r) { + defer r.Body.Close() + var body models.SubnetRegisterRequest + if err := route.Consumer.Consume(r.Body, &body); err != nil { + if err == io.EOF { + res = append(res, errors.Required("body", "body", "")) + } else { + 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(context.Background()) + if err := body.ContextValidate(ctx, route.Formats); err != nil { + res = append(res, err) + } + + if len(res) == 0 { + o.Body = &body + } + } + } else { + res = append(res, errors.Required("body", "body", "")) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/restapi/operations/admin_api/subnet_register_responses.go b/restapi/operations/admin_api/subnet_register_responses.go new file mode 100644 index 000000000..ae20740c1 --- /dev/null +++ b/restapi/operations/admin_api/subnet_register_responses.go @@ -0,0 +1,113 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// + +package admin_api + +// 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" + + "github.com/minio/console/models" +) + +// SubnetRegisterOKCode is the HTTP code returned for type SubnetRegisterOK +const SubnetRegisterOKCode int = 200 + +/*SubnetRegisterOK A successful response. + +swagger:response subnetRegisterOK +*/ +type SubnetRegisterOK struct { +} + +// NewSubnetRegisterOK creates SubnetRegisterOK with default headers values +func NewSubnetRegisterOK() *SubnetRegisterOK { + + return &SubnetRegisterOK{} +} + +// WriteResponse to the client +func (o *SubnetRegisterOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + + rw.WriteHeader(200) +} + +/*SubnetRegisterDefault Generic error response. + +swagger:response subnetRegisterDefault +*/ +type SubnetRegisterDefault struct { + _statusCode int + + /* + In: Body + */ + Payload *models.Error `json:"body,omitempty"` +} + +// NewSubnetRegisterDefault creates SubnetRegisterDefault with default headers values +func NewSubnetRegisterDefault(code int) *SubnetRegisterDefault { + if code <= 0 { + code = 500 + } + + return &SubnetRegisterDefault{ + _statusCode: code, + } +} + +// WithStatusCode adds the status to the subnet register default response +func (o *SubnetRegisterDefault) WithStatusCode(code int) *SubnetRegisterDefault { + o._statusCode = code + return o +} + +// SetStatusCode sets the status to the subnet register default response +func (o *SubnetRegisterDefault) SetStatusCode(code int) { + o._statusCode = code +} + +// WithPayload adds the payload to the subnet register default response +func (o *SubnetRegisterDefault) WithPayload(payload *models.Error) *SubnetRegisterDefault { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the subnet register default response +func (o *SubnetRegisterDefault) SetPayload(payload *models.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *SubnetRegisterDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(o._statusCode) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/restapi/operations/admin_api/subnet_register_urlbuilder.go b/restapi/operations/admin_api/subnet_register_urlbuilder.go new file mode 100644 index 000000000..00260d079 --- /dev/null +++ b/restapi/operations/admin_api/subnet_register_urlbuilder.go @@ -0,0 +1,104 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// + +package admin_api + +// 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" +) + +// SubnetRegisterURL generates an URL for the subnet register operation +type SubnetRegisterURL 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 *SubnetRegisterURL) WithBasePath(bp string) *SubnetRegisterURL { + 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 *SubnetRegisterURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *SubnetRegisterURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/subnet/register" + + _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 *SubnetRegisterURL) 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 *SubnetRegisterURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *SubnetRegisterURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on SubnetRegisterURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on SubnetRegisterURL") + } + + 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 *SubnetRegisterURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/restapi/operations/console_api.go b/restapi/operations/console_api.go index 5b1957492..93053bef1 100644 --- a/restapi/operations/console_api.go +++ b/restapi/operations/console_api.go @@ -350,8 +350,20 @@ func NewConsoleAPI(spec *loads.Document) *ConsoleAPI { UserAPIShareObjectHandler: user_api.ShareObjectHandlerFunc(func(params user_api.ShareObjectParams, principal *models.Principal) middleware.Responder { return middleware.NotImplemented("operation user_api.ShareObject has not yet been implemented") }), - AdminAPISubscriptionInfoHandler: admin_api.SubscriptionInfoHandlerFunc(func(params admin_api.SubscriptionInfoParams, principal *models.Principal) middleware.Responder { - return middleware.NotImplemented("operation admin_api.SubscriptionInfo has not yet been implemented") + AdminAPISubnetInfoHandler: admin_api.SubnetInfoHandlerFunc(func(params admin_api.SubnetInfoParams, principal *models.Principal) middleware.Responder { + return middleware.NotImplemented("operation admin_api.SubnetInfo has not yet been implemented") + }), + AdminAPISubnetLoginHandler: admin_api.SubnetLoginHandlerFunc(func(params admin_api.SubnetLoginParams, principal *models.Principal) middleware.Responder { + return middleware.NotImplemented("operation admin_api.SubnetLogin has not yet been implemented") + }), + AdminAPISubnetLoginMFAHandler: admin_api.SubnetLoginMFAHandlerFunc(func(params admin_api.SubnetLoginMFAParams, principal *models.Principal) middleware.Responder { + return middleware.NotImplemented("operation admin_api.SubnetLoginMFA has not yet been implemented") + }), + AdminAPISubnetRegTokenHandler: admin_api.SubnetRegTokenHandlerFunc(func(params admin_api.SubnetRegTokenParams, principal *models.Principal) middleware.Responder { + return middleware.NotImplemented("operation admin_api.SubnetRegToken has not yet been implemented") + }), + AdminAPISubnetRegisterHandler: admin_api.SubnetRegisterHandlerFunc(func(params admin_api.SubnetRegisterParams, principal *models.Principal) middleware.Responder { + return middleware.NotImplemented("operation admin_api.SubnetRegister has not yet been implemented") }), AdminAPITiersListHandler: admin_api.TiersListHandlerFunc(func(params admin_api.TiersListParams, principal *models.Principal) middleware.Responder { return middleware.NotImplemented("operation admin_api.TiersList has not yet been implemented") @@ -616,8 +628,16 @@ type ConsoleAPI struct { AdminAPISetPolicyMultipleHandler admin_api.SetPolicyMultipleHandler // UserAPIShareObjectHandler sets the operation handler for the share object operation UserAPIShareObjectHandler user_api.ShareObjectHandler - // AdminAPISubscriptionInfoHandler sets the operation handler for the subscription info operation - AdminAPISubscriptionInfoHandler admin_api.SubscriptionInfoHandler + // AdminAPISubnetInfoHandler sets the operation handler for the subnet info operation + AdminAPISubnetInfoHandler admin_api.SubnetInfoHandler + // AdminAPISubnetLoginHandler sets the operation handler for the subnet login operation + AdminAPISubnetLoginHandler admin_api.SubnetLoginHandler + // AdminAPISubnetLoginMFAHandler sets the operation handler for the subnet login m f a operation + AdminAPISubnetLoginMFAHandler admin_api.SubnetLoginMFAHandler + // AdminAPISubnetRegTokenHandler sets the operation handler for the subnet reg token operation + AdminAPISubnetRegTokenHandler admin_api.SubnetRegTokenHandler + // AdminAPISubnetRegisterHandler sets the operation handler for the subnet register operation + AdminAPISubnetRegisterHandler admin_api.SubnetRegisterHandler // AdminAPITiersListHandler sets the operation handler for the tiers list operation AdminAPITiersListHandler admin_api.TiersListHandler // UserAPIUpdateBucketLifecycleHandler sets the operation handler for the update bucket lifecycle operation @@ -1002,8 +1022,20 @@ func (o *ConsoleAPI) Validate() error { if o.UserAPIShareObjectHandler == nil { unregistered = append(unregistered, "user_api.ShareObjectHandler") } - if o.AdminAPISubscriptionInfoHandler == nil { - unregistered = append(unregistered, "admin_api.SubscriptionInfoHandler") + if o.AdminAPISubnetInfoHandler == nil { + unregistered = append(unregistered, "admin_api.SubnetInfoHandler") + } + if o.AdminAPISubnetLoginHandler == nil { + unregistered = append(unregistered, "admin_api.SubnetLoginHandler") + } + if o.AdminAPISubnetLoginMFAHandler == nil { + unregistered = append(unregistered, "admin_api.SubnetLoginMFAHandler") + } + if o.AdminAPISubnetRegTokenHandler == nil { + unregistered = append(unregistered, "admin_api.SubnetRegTokenHandler") + } + if o.AdminAPISubnetRegisterHandler == nil { + unregistered = append(unregistered, "admin_api.SubnetRegisterHandler") } if o.AdminAPITiersListHandler == nil { unregistered = append(unregistered, "admin_api.TiersListHandler") @@ -1508,7 +1540,23 @@ func (o *ConsoleAPI) initHandlerCache() { if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) } - o.handlers["GET"]["/subscription/info"] = admin_api.NewSubscriptionInfo(o.context, o.AdminAPISubscriptionInfoHandler) + o.handlers["GET"]["/subnet/info"] = admin_api.NewSubnetInfo(o.context, o.AdminAPISubnetInfoHandler) + if o.handlers["POST"] == nil { + o.handlers["POST"] = make(map[string]http.Handler) + } + o.handlers["POST"]["/subnet/login"] = admin_api.NewSubnetLogin(o.context, o.AdminAPISubnetLoginHandler) + if o.handlers["POST"] == nil { + o.handlers["POST"] = make(map[string]http.Handler) + } + o.handlers["POST"]["/subnet/login/mfa"] = admin_api.NewSubnetLoginMFA(o.context, o.AdminAPISubnetLoginMFAHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } + o.handlers["GET"]["/subnet/registration-token"] = admin_api.NewSubnetRegToken(o.context, o.AdminAPISubnetRegTokenHandler) + if o.handlers["POST"] == nil { + o.handlers["POST"] = make(map[string]http.Handler) + } + o.handlers["POST"]["/subnet/register"] = admin_api.NewSubnetRegister(o.context, o.AdminAPISubnetRegisterHandler) if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) } diff --git a/swagger-console.yml b/swagger-console.yml index c2cda1970..4b56e1d57 100644 --- a/swagger-console.yml +++ b/swagger-console.yml @@ -19,7 +19,7 @@ securityDefinitions: tokenUrl: http://min.io # Apply the key security definition to all APIs security: - - key: [] + - key: [ ] paths: /login: get: @@ -35,7 +35,7 @@ paths: schema: $ref: "#/definitions/error" # Exclude this API from the authentication requirement - security: [] + security: [ ] tags: - UserAPI post: @@ -55,7 +55,7 @@ paths: schema: $ref: "#/definitions/error" # Exclude this API from the authentication requirement - security: [] + security: [ ] tags: - UserAPI /login/oauth2/auth: @@ -75,7 +75,7 @@ paths: description: Generic error response. schema: $ref: "#/definitions/error" - security: [] + security: [ ] tags: - UserAPI @@ -2067,11 +2067,25 @@ paths: $ref: "#/definitions/error" tags: - AdminAPI - - /subscription/info: + /subnet/registration-token: get: - summary: Subscription info - operationId: SubscriptionInfo + summary: Subnet registraton token + operationId: SubnetRegToken + responses: + 200: + description: A successful response. + schema: + $ref: "#/definitions/SubnetRegTokenResponse" + default: + description: Generic error response. + schema: + $ref: "#/definitions/error" + tags: + - AdminAPI + /subnet/info: + get: + summary: Subnet info + operationId: SubnetInfo responses: 200: description: A successful response. @@ -2083,6 +2097,71 @@ paths: $ref: "#/definitions/error" tags: - AdminAPI + /subnet/register: + post: + summary: Register cluster with Subnet + operationId: SubnetRegister + parameters: + - name: body + in: body + required: true + schema: + $ref: "#/definitions/subnetRegisterRequest" + responses: + 200: + description: A successful response. + # schema: + # $ref: "#/definitions/subnetRegisterResponse" + default: + description: Generic error response. + schema: + $ref: "#/definitions/error" + tags: + - AdminAPI + + /subnet/login: + post: + summary: Login to subnet + operationId: SubnetLogin + parameters: + - name: body + in: body + required: true + schema: + $ref: "#/definitions/subnetLoginRequest" + responses: + 200: + description: A successful response. + schema: + $ref: "#/definitions/subnetLoginResponse" + default: + description: Generic error response. + schema: + $ref: "#/definitions/error" + tags: + - AdminAPI + + /subnet/login/mfa: + post: + summary: Login to subnet using mfa + operationId: SubnetLoginMFA + parameters: + - name: body + in: body + required: true + schema: + $ref: "#/definitions/subnetLoginMFARequest" + responses: + 200: + description: A successful response. + schema: + $ref: "#/definitions/subnetLoginResponse" + default: + description: Generic error response. + schema: + $ref: "#/definitions/error" + tags: + - AdminAPI /admin/info: get: @@ -2386,7 +2465,7 @@ paths: - name: order in: query type: string - enum: [timeDesc, timeAsc] + enum: [ timeDesc, timeAsc ] default: timeDesc - name: timeStart in: query @@ -3152,7 +3231,7 @@ definitions: properties: loginStrategy: type: string - enum: [form, redirect, service-account] + enum: [ form, redirect, service-account ] redirect: type: string loginOauth2AuthRequest: @@ -3238,7 +3317,7 @@ definitions: type: string status: type: string - enum: [ok] + enum: [ ok ] operator: type: boolean distributedMode: @@ -3259,7 +3338,7 @@ definitions: type: string values: type: array - items: {} + items: { } resultTarget: type: object properties: @@ -3539,7 +3618,7 @@ definitions: type: string service: type: string - enum: [replication] + enum: [ replication ] syncMode: type: string bandwidth: @@ -4015,4 +4094,75 @@ definitions: properties: objectMetadata: type: object - additionalProperties: true \ No newline at end of file + additionalProperties: true + + subnetLoginResponse: + type: object + properties: + access_token: + type: string + organizations: + type: array + items: + $ref: "#/definitions/subnetOrganization" + mfa_token: + type: string + registered: + type: boolean + + subnetLoginRequest: + type: object + properties: + username: + type: string + password: + type: string + apiKey: + type: string + + subnetLoginMFARequest: + type: object + required: + - username + - otp + - mfa_token + properties: + username: + type: string + otp: + type: string + mfa_token: + type: string + + subnetRegisterRequest: + type: object + required: + - token + - account_id + properties: + token: + type: string + account_id: + type: string + + SubnetRegTokenResponse: + type: object + properties: + regToken: + type: string + + subnetOrganization: + type: object + properties: + userId: + type: integer + accountId: + type: integer + subscriptionStatus: + type: string + isAccountOwner: + type: boolean + company: + type: string + shortName: + type: string \ No newline at end of file