[CLD-7421][CLD-7420] Deprecate Self Serve: First Pass (#26668)
* Deprecate Self Serve: First Pass * Fix ci * Fix more ci * Remmove outdated server tests * Fix a missed spot opening purchase modal in Self Hosted * Fix i18n * Clean up some more server code, fix webapp test * Fix alignment of button * Fix linter * Fix i18n server side * Add back translation * Remove client functions * Put back client functions --------- Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
@@ -13,7 +13,6 @@ import (
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/audit"
|
||||
"github.com/mattermost/mattermost/server/v8/platform/shared/web"
|
||||
)
|
||||
|
||||
@@ -25,11 +24,6 @@ func (api *API) InitCloud() {
|
||||
|
||||
api.BaseRoutes.Cloud.Handle("/products/selfhosted", api.APISessionRequired(getSelfHostedProducts)).Methods("GET")
|
||||
|
||||
// POST /api/v4/cloud/payment
|
||||
// POST /api/v4/cloud/payment/confirm
|
||||
api.BaseRoutes.Cloud.Handle("/payment", api.APISessionRequired(createCustomerPayment)).Methods("POST")
|
||||
api.BaseRoutes.Cloud.Handle("/payment/confirm", api.APISessionRequired(confirmCustomerPayment)).Methods("POST")
|
||||
|
||||
// GET /api/v4/cloud/customer
|
||||
// PUT /api/v4/cloud/customer
|
||||
// PUT /api/v4/cloud/customer/address
|
||||
@@ -42,10 +36,6 @@ func (api *API) InitCloud() {
|
||||
api.BaseRoutes.Cloud.Handle("/subscription/invoices", api.APISessionRequired(getInvoicesForSubscription)).Methods("GET")
|
||||
api.BaseRoutes.Cloud.Handle("/subscription/invoices/{invoice_id:[_A-Za-z0-9]+}/pdf", api.APISessionRequired(getSubscriptionInvoicePDF)).Methods("GET")
|
||||
api.BaseRoutes.Cloud.Handle("/subscription/self-serve-status", api.APISessionRequired(getLicenseSelfServeStatus)).Methods("GET")
|
||||
api.BaseRoutes.Cloud.Handle("/subscription", api.APISessionRequired(changeSubscription)).Methods("PUT")
|
||||
|
||||
// GET /api/v4/cloud/request-trial
|
||||
api.BaseRoutes.Cloud.Handle("/request-trial", api.APISessionRequired(requestCloudTrial)).Methods("PUT")
|
||||
|
||||
// GET /api/v4/cloud/validate-business-email
|
||||
api.BaseRoutes.Cloud.Handle("/validate-business-email", api.APISessionRequired(validateBusinessEmail)).Methods("POST")
|
||||
@@ -59,8 +49,6 @@ func (api *API) InitCloud() {
|
||||
|
||||
// GET /api/v4/cloud/cws-health-check
|
||||
api.BaseRoutes.Cloud.Handle("/check-cws-connection", api.APIHandler(handleCheckCWSConnection)).Methods("GET")
|
||||
|
||||
api.BaseRoutes.Cloud.Handle("/delete-workspace", api.APISessionRequired(selfServeDeleteWorkspace)).Methods("DELETE")
|
||||
}
|
||||
|
||||
func ensureCloudInterface(c *Context, where string) bool {
|
||||
@@ -134,131 +122,6 @@ func getSubscription(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
w.Write(json)
|
||||
}
|
||||
|
||||
func changeSubscription(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
ensured := ensureCloudInterface(c, "Api4.changeSubscription")
|
||||
if !ensured {
|
||||
return
|
||||
}
|
||||
userId := c.AppContext.Session().UserId
|
||||
|
||||
if !c.App.Channels().License().IsCloud() {
|
||||
c.Err = model.NewAppError("Api4.changeSubscription", "api.cloud.license_error", nil, "", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionSysconsoleWriteBilling) {
|
||||
c.SetPermissionError(model.PermissionSysconsoleWriteBilling)
|
||||
return
|
||||
}
|
||||
|
||||
bodyBytes, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("Api4.changeSubscription", "api.cloud.app_error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
var subscriptionChange *model.SubscriptionChange
|
||||
if err = json.Unmarshal(bodyBytes, &subscriptionChange); err != nil || subscriptionChange == nil {
|
||||
c.Err = model.NewAppError("Api4.changeSubscription", "api.cloud.app_error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
currentSubscription, appErr := c.App.Cloud().GetSubscription(userId)
|
||||
if appErr != nil {
|
||||
c.Err = model.NewAppError("Api4.changeSubscription", "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(appErr)
|
||||
return
|
||||
}
|
||||
|
||||
changedSub, err := c.App.Cloud().ChangeSubscription(userId, currentSubscription.ID, subscriptionChange)
|
||||
if err != nil {
|
||||
appErr := model.NewAppError("Api4.changeSubscription", "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
if err.Error() == "compliance-failed" {
|
||||
c.Logger.Error("Compliance check failed", mlog.Err(err))
|
||||
appErr.StatusCode = http.StatusUnprocessableEntity
|
||||
}
|
||||
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
if subscriptionChange.Feedback != nil {
|
||||
c.App.Srv().GetTelemetryService().SendTelemetry("downgrade_feedback", subscriptionChange.Feedback.ToMap())
|
||||
}
|
||||
|
||||
json, err := json.Marshal(changedSub)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("Api4.changeSubscription", "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
product, err := c.App.Cloud().GetCloudProduct(c.AppContext.Session().UserId, subscriptionChange.ProductID)
|
||||
if err != nil || product == nil {
|
||||
c.Logger.Error("Error finding the new cloud product", mlog.Err(err))
|
||||
}
|
||||
|
||||
if product.SKU == string(model.SkuCloudStarter) {
|
||||
w.Write(json)
|
||||
return
|
||||
}
|
||||
|
||||
isYearly := product.IsYearly()
|
||||
|
||||
// Log failures for purchase confirmation email, but don't show an error to the user so as not to confuse them
|
||||
// At this point, the upgrade is complete.
|
||||
if appErr := c.App.SendUpgradeConfirmationEmail(isYearly); appErr != nil {
|
||||
c.Logger.Error("Error sending purchase confirmation email", mlog.Err(appErr))
|
||||
}
|
||||
|
||||
w.Write(json)
|
||||
}
|
||||
|
||||
func requestCloudTrial(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
ensured := ensureCloudInterface(c, "Api4.requestCloudTrial")
|
||||
if !ensured {
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.Channels().License().IsCloud() {
|
||||
c.Err = model.NewAppError("Api4.requestCloudTrial", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionSysconsoleWriteBilling) {
|
||||
c.SetPermissionError(model.PermissionSysconsoleWriteBilling)
|
||||
return
|
||||
}
|
||||
|
||||
// check if the email needs to be set
|
||||
bodyBytes, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("Api4.requestCloudTrial", "api.cloud.app_error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||
return
|
||||
}
|
||||
// this value will not be empty when both emails (user admin and CWS customer) are not business email and
|
||||
// a new business email was provided via the request business email modal
|
||||
var startTrialRequest *model.StartCloudTrialRequest
|
||||
if err = json.Unmarshal(bodyBytes, &startTrialRequest); err != nil || startTrialRequest == nil {
|
||||
c.Err = model.NewAppError("Api4.requestCloudTrial", "api.cloud.app_error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
changedSub, err := c.App.Cloud().RequestCloudTrial(c.AppContext.Session().UserId, startTrialRequest.SubscriptionID, startTrialRequest.Email)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("Api4.requestCloudTrial", "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
json, err := json.Marshal(changedSub)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("Api4.requestCloudTrial", "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
defer c.App.Srv().Cloud.InvalidateCaches()
|
||||
|
||||
w.Write(json)
|
||||
}
|
||||
|
||||
func validateBusinessEmail(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
ensured := ensureCloudInterface(c, "Api4.validateBusinessEmail")
|
||||
if !ensured {
|
||||
@@ -635,84 +498,6 @@ func updateCloudCustomerAddress(c *Context, w http.ResponseWriter, r *http.Reque
|
||||
w.Write(json)
|
||||
}
|
||||
|
||||
func createCustomerPayment(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
ensured := ensureCloudInterface(c, "Api4.createCustomerPayment")
|
||||
if !ensured {
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.Channels().License().IsCloud() {
|
||||
c.Err = model.NewAppError("Api4.createCustomerPayment", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionSysconsoleWriteBilling) {
|
||||
c.SetPermissionError(model.PermissionSysconsoleWriteBilling)
|
||||
return
|
||||
}
|
||||
|
||||
auditRec := c.MakeAuditRecord("createCustomerPayment", audit.Fail)
|
||||
defer c.LogAuditRec(auditRec)
|
||||
|
||||
intent, err := c.App.Cloud().CreateCustomerPayment(c.AppContext.Session().UserId)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("Api4.createCustomerPayment", "api.cloud.request_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
json, err := json.Marshal(intent)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("Api4.createCustomerPayment", "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
auditRec.Success()
|
||||
|
||||
w.Write(json)
|
||||
}
|
||||
|
||||
func confirmCustomerPayment(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
ensured := ensureCloudInterface(c, "Api4.confirmCustomerPayment")
|
||||
if !ensured {
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.Channels().License().IsCloud() {
|
||||
c.Err = model.NewAppError("Api4.confirmCustomerPayment", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionSysconsoleWriteBilling) {
|
||||
c.SetPermissionError(model.PermissionSysconsoleWriteBilling)
|
||||
return
|
||||
}
|
||||
|
||||
auditRec := c.MakeAuditRecord("confirmCustomerPayment", audit.Fail)
|
||||
defer c.LogAuditRec(auditRec)
|
||||
|
||||
bodyBytes, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("Api4.confirmCustomerPayment", "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
var confirmRequest *model.ConfirmPaymentMethodRequest
|
||||
if err = json.Unmarshal(bodyBytes, &confirmRequest); err != nil || confirmRequest == nil {
|
||||
c.Err = model.NewAppError("Api4.confirmCustomerPayment", "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
err = c.App.Cloud().ConfirmCustomerPayment(c.AppContext.Session().UserId, confirmRequest)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("Api4.createCustomerPayment", "api.cloud.request_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
auditRec.Success()
|
||||
|
||||
ReturnStatusOK(w)
|
||||
}
|
||||
|
||||
func getInvoicesForSubscription(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
ensured := ensureCloudInterface(c, "Api4.getInvoicesForSubscription")
|
||||
if !ensured {
|
||||
@@ -809,40 +594,6 @@ func handleCWSWebhook(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
switch event.Event {
|
||||
case model.EventTypeFailedPayment:
|
||||
if nErr := c.App.SendPaymentFailedEmail(event.FailedPayment); nErr != nil {
|
||||
c.Err = nErr
|
||||
return
|
||||
}
|
||||
case model.EventTypeFailedPaymentNoCard:
|
||||
if nErr := c.App.SendNoCardPaymentFailedEmail(); nErr != nil {
|
||||
c.Err = nErr
|
||||
return
|
||||
}
|
||||
case model.EventTypeSendUpgradeConfirmationEmail:
|
||||
|
||||
// isYearly determines whether to send the yearly or monthly Upgrade email
|
||||
isYearly := false
|
||||
if event.Subscription != nil && event.CloudWorkspaceOwner != nil {
|
||||
user, appErr := c.App.GetUserByUsername(event.CloudWorkspaceOwner.UserName)
|
||||
if appErr != nil {
|
||||
c.Err = model.NewAppError("Api4.handleCWSWebhook", appErr.Id, nil, "", appErr.StatusCode).Wrap(appErr)
|
||||
return
|
||||
}
|
||||
|
||||
// Get the current cloud product to determine whether it's a monthly or yearly product
|
||||
product, err := c.App.Cloud().GetCloudProduct(user.Id, event.Subscription.ProductID)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("Api4.handleCWSWebhook", "api.cloud.request_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
isYearly = product.IsYearly()
|
||||
}
|
||||
|
||||
if nErr := c.App.SendUpgradeConfirmationEmail(isYearly); nErr != nil {
|
||||
c.Err = nErr
|
||||
return
|
||||
}
|
||||
case model.EventTypeSendAdminWelcomeEmail:
|
||||
user, appErr := c.App.GetUserByUsername(event.CloudWorkspaceOwner.UserName)
|
||||
if appErr != nil {
|
||||
@@ -868,19 +619,6 @@ func handleCWSWebhook(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.Err = model.NewAppError("SendCloudWelcomeEmail", "api.user.send_cloud_welcome_email.error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
case model.EventTypeTriggerDelinquencyEmail:
|
||||
var emailToTrigger model.DelinquencyEmail
|
||||
if event.DelinquencyEmail != nil {
|
||||
emailToTrigger = model.DelinquencyEmail(event.DelinquencyEmail.EmailToTrigger)
|
||||
} else {
|
||||
c.Err = model.NewAppError("Api4.handleCWSWebhook", "api.cloud.delinquency_email.missing_email_to_trigger", nil, "", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if nErr := c.App.SendDelinquencyEmail(emailToTrigger); nErr != nil {
|
||||
c.Err = nErr
|
||||
return
|
||||
}
|
||||
|
||||
default:
|
||||
c.Err = model.NewAppError("Api4.handleCWSWebhook", "api.cloud.cws_webhook_event_missing_error", nil, "", http.StatusNotFound)
|
||||
return
|
||||
@@ -902,37 +640,3 @@ func handleCheckCWSConnection(c *Context, w http.ResponseWriter, r *http.Request
|
||||
|
||||
ReturnStatusOK(w)
|
||||
}
|
||||
|
||||
func selfServeDeleteWorkspace(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
ensured := ensureCloudInterface(c, "Api4.selfServeDeleteWorkspace")
|
||||
if !ensured {
|
||||
return
|
||||
}
|
||||
|
||||
bodyBytes, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("Api4.selfServeDeleteWorkspace", "api.cloud.app_error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||
return
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionSysconsoleWriteBilling) {
|
||||
c.SetPermissionError(model.PermissionSysconsoleWriteBilling)
|
||||
return
|
||||
}
|
||||
|
||||
var deleteRequest *model.WorkspaceDeletionRequest
|
||||
if err = json.Unmarshal(bodyBytes, &deleteRequest); err != nil || deleteRequest == nil {
|
||||
c.Err = model.NewAppError("Api4.selfServeDeleteWorkspace", "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := c.App.Cloud().SelfServeDeleteWorkspace(c.AppContext.Session().UserId, deleteRequest); err != nil {
|
||||
c.Err = model.NewAppError("Api4.selfServeDeleteWorkspace", "api.server.cws.delete_workspace.app_error", nil, "CWS Server failed to delete workspace.", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
c.App.Srv().GetTelemetryService().SendTelemetry("delete_workspace_feedback", deleteRequest.Feedback.ToMap())
|
||||
|
||||
ReturnStatusOK(w)
|
||||
}
|
||||
|
||||
@@ -16,119 +16,6 @@ import (
|
||||
"github.com/mattermost/mattermost/server/v8/einterfaces/mocks"
|
||||
)
|
||||
|
||||
func Test_getCloudLimits(t *testing.T) {
|
||||
t.Run("no license returns not implemented", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
cloud := &mocks.CloudInterface{}
|
||||
cloud.Mock.On("GetCloudLimits", mock.Anything).Return(nil, errors.New("Unable to get limits"))
|
||||
|
||||
cloudImpl := th.App.Srv().Cloud
|
||||
defer func() {
|
||||
th.App.Srv().Cloud = cloudImpl
|
||||
}()
|
||||
th.App.Srv().Cloud = cloud
|
||||
|
||||
th.App.Srv().RemoveLicense()
|
||||
|
||||
th.Client.Login(context.Background(), th.BasicUser.Email, th.BasicUser.Password)
|
||||
|
||||
limits, r, err := th.Client.GetProductLimits(context.Background())
|
||||
require.Error(t, err)
|
||||
require.Nil(t, limits)
|
||||
require.Equal(t, http.StatusForbidden, r.StatusCode, "Expected 403 forbidden")
|
||||
})
|
||||
|
||||
t.Run("non cloud license returns not implemented", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
cloud := &mocks.CloudInterface{}
|
||||
cloud.Mock.On("GetCloudLimits", mock.Anything).Return(nil, errors.New("Unable to get limits"))
|
||||
|
||||
cloudImpl := th.App.Srv().Cloud
|
||||
defer func() {
|
||||
th.App.Srv().Cloud = cloudImpl
|
||||
}()
|
||||
th.App.Srv().Cloud = cloud
|
||||
|
||||
th.App.Srv().SetLicense(model.NewTestLicense())
|
||||
|
||||
th.Client.Login(context.Background(), th.BasicUser.Email, th.BasicUser.Password)
|
||||
|
||||
limits, r, err := th.Client.GetProductLimits(context.Background())
|
||||
require.Error(t, err)
|
||||
require.Nil(t, limits)
|
||||
require.Equal(t, http.StatusForbidden, r.StatusCode, "Expected 403 forbidden")
|
||||
})
|
||||
|
||||
t.Run("error fetching limits returns internal server error", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.App.Srv().SetLicense(model.NewTestLicense("cloud"))
|
||||
|
||||
cloud := &mocks.CloudInterface{}
|
||||
cloud.Mock.On("GetCloudLimits", mock.Anything).Return(nil, errors.New("Unable to get limits"))
|
||||
|
||||
cloudImpl := th.App.Srv().Cloud
|
||||
defer func() {
|
||||
th.App.Srv().Cloud = cloudImpl
|
||||
}()
|
||||
th.App.Srv().Cloud = cloud
|
||||
|
||||
th.Client.Login(context.Background(), th.BasicUser.Email, th.BasicUser.Password)
|
||||
|
||||
limits, r, err := th.Client.GetProductLimits(context.Background())
|
||||
require.Error(t, err)
|
||||
require.Nil(t, limits)
|
||||
require.Equal(t, http.StatusInternalServerError, r.StatusCode, "Expected 500 Internal Server Error")
|
||||
})
|
||||
|
||||
t.Run("unauthenticated users can not access", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.Client.Logout(context.Background())
|
||||
|
||||
limits, r, err := th.Client.GetProductLimits(context.Background())
|
||||
require.Error(t, err)
|
||||
require.Nil(t, limits)
|
||||
require.Equal(t, http.StatusUnauthorized, r.StatusCode, "Expected 401 Unauthorized")
|
||||
})
|
||||
|
||||
t.Run("good request with cloud server", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.App.Srv().SetLicense(model.NewTestLicense("cloud"))
|
||||
|
||||
cloud := &mocks.CloudInterface{}
|
||||
ten := 10
|
||||
mockLimits := &model.ProductLimits{
|
||||
Messages: &model.MessagesLimits{
|
||||
History: &ten,
|
||||
},
|
||||
}
|
||||
cloud.Mock.On("GetCloudLimits", mock.Anything).Return(mockLimits, nil)
|
||||
|
||||
cloudImpl := th.App.Srv().Cloud
|
||||
defer func() {
|
||||
th.App.Srv().Cloud = cloudImpl
|
||||
}()
|
||||
th.App.Srv().Cloud = cloud
|
||||
|
||||
th.Client.Login(context.Background(), th.BasicUser.Email, th.BasicUser.Password)
|
||||
|
||||
limits, r, err := th.Client.GetProductLimits(context.Background())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, r.StatusCode, "Expected 200 OK")
|
||||
require.Equal(t, mockLimits, limits)
|
||||
require.Equal(t, *mockLimits.Messages.History, *limits.Messages.History)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_GetSubscription(t *testing.T) {
|
||||
deliquencySince := int64(2000000000)
|
||||
|
||||
@@ -215,119 +102,6 @@ func Test_GetSubscription(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_requestTrial(t *testing.T) {
|
||||
subscription := &model.Subscription{
|
||||
ID: "MySubscriptionID",
|
||||
CustomerID: "MyCustomer",
|
||||
ProductID: "SomeProductId",
|
||||
AddOns: []string{},
|
||||
StartAt: 1000000000,
|
||||
EndAt: 2000000000,
|
||||
CreateAt: 1000000000,
|
||||
Seats: 10,
|
||||
DNS: "some.dns.server",
|
||||
}
|
||||
|
||||
newValidBusinessEmail := model.StartCloudTrialRequest{Email: ""}
|
||||
|
||||
t.Run("NON Admin users are UNABLE to request the trial", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.Client.Login(context.Background(), th.BasicUser.Email, th.BasicUser.Password)
|
||||
|
||||
th.App.Srv().SetLicense(model.NewTestLicense("cloud"))
|
||||
|
||||
cloud := mocks.CloudInterface{}
|
||||
|
||||
cloud.Mock.On("GetSubscription", mock.Anything).Return(subscription, nil)
|
||||
cloud.Mock.On("RequestCloudTrial", mock.Anything, mock.Anything, "").Return(subscription, nil)
|
||||
cloud.Mock.On("InvalidateCaches").Return(nil)
|
||||
|
||||
cloudImpl := th.App.Srv().Cloud
|
||||
defer func() {
|
||||
th.App.Srv().Cloud = cloudImpl
|
||||
}()
|
||||
th.App.Srv().Cloud = &cloud
|
||||
|
||||
subscriptionChanged, r, err := th.Client.RequestCloudTrial(context.Background(), &newValidBusinessEmail)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, subscriptionChanged)
|
||||
require.Equal(t, http.StatusForbidden, r.StatusCode, "403 Forbidden")
|
||||
})
|
||||
|
||||
t.Run("ADMIN user are ABLE to request the trial", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.Client.Login(context.Background(), th.BasicUser.Email, th.BasicUser.Password)
|
||||
|
||||
th.App.Srv().SetLicense(model.NewTestLicense("cloud"))
|
||||
|
||||
cloud := mocks.CloudInterface{}
|
||||
|
||||
cloud.Mock.On("GetSubscription", mock.Anything).Return(subscription, nil)
|
||||
cloud.Mock.On("RequestCloudTrial", mock.Anything, mock.Anything, "").Return(subscription, nil)
|
||||
cloud.Mock.On("InvalidateCaches").Return(nil)
|
||||
|
||||
cloudImpl := th.App.Srv().Cloud
|
||||
defer func() {
|
||||
th.App.Srv().Cloud = cloudImpl
|
||||
}()
|
||||
th.App.Srv().Cloud = &cloud
|
||||
|
||||
subscriptionChanged, r, err := th.SystemAdminClient.RequestCloudTrial(context.Background(), &newValidBusinessEmail)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, subscriptionChanged, subscription)
|
||||
require.Equal(t, http.StatusOK, r.StatusCode, "Status OK")
|
||||
})
|
||||
|
||||
t.Run("ADMIN user are ABLE to request the trial with valid business email", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
// patch the customer with the additional contact updated with the valid business email
|
||||
newValidBusinessEmail.Email = *model.NewString("valid.email@mattermost.com")
|
||||
|
||||
th.Client.Login(context.Background(), th.BasicUser.Email, th.BasicUser.Password)
|
||||
|
||||
th.App.Srv().SetLicense(model.NewTestLicense("cloud"))
|
||||
|
||||
cloud := mocks.CloudInterface{}
|
||||
|
||||
cloud.Mock.On("GetSubscription", mock.Anything).Return(subscription, nil)
|
||||
cloud.Mock.On("RequestCloudTrial", mock.Anything, mock.Anything, "valid.email@mattermost.com").Return(subscription, nil)
|
||||
cloud.Mock.On("InvalidateCaches").Return(nil)
|
||||
|
||||
cloudImpl := th.App.Srv().Cloud
|
||||
defer func() {
|
||||
th.App.Srv().Cloud = cloudImpl
|
||||
}()
|
||||
th.App.Srv().Cloud = &cloud
|
||||
|
||||
subscriptionChanged, r, err := th.SystemAdminClient.RequestCloudTrial(context.Background(), &newValidBusinessEmail)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, subscriptionChanged, subscription)
|
||||
require.Equal(t, http.StatusOK, r.StatusCode, "Status OK")
|
||||
})
|
||||
|
||||
t.Run("Empty body returns bad request", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.Client.Login(context.Background(), th.BasicUser.Email, th.BasicUser.Password)
|
||||
|
||||
th.App.Srv().SetLicense(model.NewTestLicense("cloud"))
|
||||
|
||||
r, err := th.SystemAdminClient.DoAPIPutBytes(context.Background(), "/cloud/request-trial", nil)
|
||||
require.Error(t, err)
|
||||
closeBody(r)
|
||||
require.Equal(t, http.StatusBadRequest, r.StatusCode, "Status Bad Request")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_validateBusinessEmail(t *testing.T) {
|
||||
t.Run("Returns forbidden for invalid business email", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
@@ -643,58 +417,6 @@ func TestGetCloudProducts(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_GetExpandStatsForSubscription(t *testing.T) {
|
||||
status := &model.SubscriptionLicenseSelfServeStatusResponse{
|
||||
IsExpandable: true,
|
||||
}
|
||||
|
||||
licenseId := "licenseID"
|
||||
|
||||
t.Run("NON Admin users are UNABLE to request expand stats for the subscription", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.Client.Login(context.Background(), th.BasicUser.Email, th.BasicUser.Password)
|
||||
|
||||
cloud := mocks.CloudInterface{}
|
||||
|
||||
cloud.Mock.On("GetLicenseSelfServeStatus", mock.Anything).Return(status, nil)
|
||||
|
||||
cloudImpl := th.App.Srv().Cloud
|
||||
defer func() {
|
||||
th.App.Srv().Cloud = cloudImpl
|
||||
}()
|
||||
th.App.Srv().Cloud = &cloud
|
||||
|
||||
checksMade, r, err := th.Client.GetSubscriptionStatus(context.Background(), licenseId)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, checksMade)
|
||||
require.Equal(t, http.StatusForbidden, r.StatusCode, "403 Forbidden")
|
||||
})
|
||||
|
||||
t.Run("Admin users are UNABLE to request licenses is expendable due missing the id", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.Client.Login(context.Background(), th.SystemAdminUser.Email, th.SystemAdminUser.Password)
|
||||
|
||||
cloud := mocks.CloudInterface{}
|
||||
|
||||
cloud.Mock.On("GetLicenseSelfServeStatus", mock.Anything).Return(status, nil)
|
||||
|
||||
cloudImpl := th.App.Srv().Cloud
|
||||
defer func() {
|
||||
th.App.Srv().Cloud = cloudImpl
|
||||
}()
|
||||
th.App.Srv().Cloud = &cloud
|
||||
|
||||
checks, r, err := th.Client.GetSubscriptionStatus(context.Background(), "")
|
||||
require.Error(t, err)
|
||||
require.Nil(t, checks)
|
||||
require.Equal(t, http.StatusBadRequest, r.StatusCode, "400 Bad Request")
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetSelfHostedProducts(t *testing.T) {
|
||||
products := []*model.Product{
|
||||
{
|
||||
|
||||
@@ -4,20 +4,11 @@
|
||||
package api4
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/utils"
|
||||
"github.com/mattermost/mattermost/server/v8/platform/shared/web"
|
||||
)
|
||||
|
||||
// APIs for self-hosted workspaces to communicate with the backing customer & payments system.
|
||||
@@ -25,276 +16,12 @@ import (
|
||||
func (api *API) InitHostedCustomer() {
|
||||
// POST /api/v4/hosted_customer/available
|
||||
api.BaseRoutes.HostedCustomer.Handle("/signup_available", api.APISessionRequired(handleSignupAvailable)).Methods("GET")
|
||||
// POST /api/v4/hosted_customer/bootstrap
|
||||
api.BaseRoutes.HostedCustomer.Handle("/bootstrap", api.APISessionRequired(selfHostedBootstrap)).Methods("POST")
|
||||
// POST /api/v4/hosted_customer/customer
|
||||
api.BaseRoutes.HostedCustomer.Handle("/customer", api.APISessionRequired(selfHostedCustomer)).Methods("POST")
|
||||
// POST /api/v4/hosted_customer/confirm
|
||||
api.BaseRoutes.HostedCustomer.Handle("/confirm", api.APISessionRequired(selfHostedConfirm)).Methods("POST")
|
||||
// POST /api.v4/hosted_customer/confirm-expand
|
||||
api.BaseRoutes.HostedCustomer.Handle("/confirm-expand", api.APISessionRequired(selfHostedConfirmExpand)).Methods("POST")
|
||||
// GET /api/v4/hosted_customer/invoices
|
||||
api.BaseRoutes.HostedCustomer.Handle("/invoices", api.APISessionRequired(selfHostedInvoices)).Methods("GET")
|
||||
// GET /api/v4/hosted_customer/invoices/{invoice_id:in_[A-Za-z0-9]+}/pdf
|
||||
api.BaseRoutes.HostedCustomer.Handle("/invoices/{invoice_id:in_[A-Za-z0-9]+}/pdf", api.APISessionRequired(selfHostedInvoicePDF)).Methods("GET")
|
||||
|
||||
api.BaseRoutes.HostedCustomer.Handle("/subscribe-newsletter", api.APIHandler(handleSubscribeToNewsletter)).Methods("POST")
|
||||
}
|
||||
|
||||
func ensureSelfHostedAdmin(c *Context, where string) {
|
||||
ensured := ensureCloudInterface(c, where)
|
||||
if !ensured {
|
||||
return
|
||||
}
|
||||
|
||||
license := c.App.Channels().License()
|
||||
|
||||
if license.IsCloud() {
|
||||
c.Err = model.NewAppError(where, "api.cloud.license_error", nil, "Cloud installations do not use this endpoint", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionSysconsoleWriteBilling) {
|
||||
c.SetPermissionError(model.PermissionSysconsoleWriteBilling)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func checkSelfHostedPurchaseEnabled(c *Context) bool {
|
||||
config := c.App.Config()
|
||||
if config == nil {
|
||||
return false
|
||||
}
|
||||
enabled := config.ServiceSettings.SelfHostedPurchase
|
||||
return enabled != nil && *enabled
|
||||
}
|
||||
|
||||
func selfHostedBootstrap(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
const where = "Api4.selfHostedBootstrap"
|
||||
if !checkSelfHostedPurchaseEnabled(c) {
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
reset := r.URL.Query().Get("reset") == "true"
|
||||
ensureSelfHostedAdmin(c, where)
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
user, userErr := c.App.GetUser(c.AppContext.Session().UserId)
|
||||
if userErr != nil {
|
||||
c.Err = userErr
|
||||
return
|
||||
}
|
||||
|
||||
signupProgress, err := c.App.Cloud().BootstrapSelfHostedSignup(model.BootstrapSelfHostedSignupRequest{Email: user.Email, Reset: reset})
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
json, err := json.Marshal(signupProgress)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(json)
|
||||
}
|
||||
|
||||
func selfHostedCustomer(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
const where = "Api4.selfHostedCustomer"
|
||||
ensureSelfHostedAdmin(c, where)
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
if !checkSelfHostedPurchaseEnabled(c) {
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
|
||||
bodyBytes, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
var form *model.SelfHostedCustomerForm
|
||||
if err = json.Unmarshal(bodyBytes, &form); err != nil || form == nil {
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
user, userErr := c.App.GetUser(c.AppContext.Session().UserId)
|
||||
if userErr != nil {
|
||||
c.Err = userErr
|
||||
return
|
||||
}
|
||||
customerResponse, err := c.App.Cloud().CreateCustomerSelfHostedSignup(*form, user.Email)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
json, err := json.Marshal(customerResponse)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError(where, "api.cloud.request_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(json)
|
||||
}
|
||||
|
||||
func selfHostedConfirm(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
const where = "Api4.selfHostedConfirm"
|
||||
ensureSelfHostedAdmin(c, where)
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
if !checkSelfHostedPurchaseEnabled(c) {
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
|
||||
bodyBytes, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
var confirm model.SelfHostedConfirmPaymentMethodRequest
|
||||
err = json.Unmarshal(bodyBytes, &confirm)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError(where, "api.cloud.request_error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
user, userErr := c.App.GetUser(c.AppContext.Session().UserId)
|
||||
if userErr != nil {
|
||||
c.Err = userErr
|
||||
return
|
||||
}
|
||||
|
||||
confirmResponse, err := c.App.Cloud().ConfirmSelfHostedSignup(confirm, user.Email)
|
||||
if err != nil {
|
||||
if confirmResponse != nil {
|
||||
c.App.NotifySelfHostedSignupProgress(confirmResponse.Progress, user.Id)
|
||||
}
|
||||
|
||||
if err.Error() == strconv.Itoa(http.StatusUnprocessableEntity) {
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusUnprocessableEntity).Wrap(err)
|
||||
return
|
||||
}
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
license, appErr := c.App.Srv().Platform().SaveLicense([]byte(confirmResponse.License))
|
||||
if appErr != nil {
|
||||
if confirmResponse != nil {
|
||||
c.App.NotifySelfHostedSignupProgress(confirmResponse.Progress, user.Id)
|
||||
}
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
clientResponse, err := json.Marshal(model.SelfHostedSignupConfirmClientResponse{
|
||||
License: utils.GetClientLicense(license),
|
||||
Progress: confirmResponse.Progress,
|
||||
})
|
||||
if err != nil {
|
||||
if confirmResponse != nil {
|
||||
c.App.NotifySelfHostedSignupProgress(confirmResponse.Progress, user.Id)
|
||||
}
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
err := c.App.Cloud().ConfirmSelfHostedSignupLicenseApplication()
|
||||
if err != nil {
|
||||
c.Logger.Warn("Unable to confirm license application", mlog.Err(err))
|
||||
}
|
||||
}()
|
||||
|
||||
_, _ = w.Write(clientResponse)
|
||||
}
|
||||
|
||||
func handleSignupAvailable(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
const where = "Api4.handleSignupAvailable"
|
||||
ensureSelfHostedAdmin(c, where)
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
if !checkSelfHostedPurchaseEnabled(c) {
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
if err := c.App.Cloud().SelfHostedSignupAvailable(); err != nil {
|
||||
if err.Error() == "upstream_off" {
|
||||
c.Err = model.NewAppError(where, "api.server.hosted_signup_unavailable.error", nil, "", http.StatusServiceUnavailable)
|
||||
} else {
|
||||
c.Err = model.NewAppError(where, "api.server.hosted_signup_unavailable.error", nil, "", http.StatusNotImplemented)
|
||||
}
|
||||
return
|
||||
}
|
||||
systemValue, err := c.App.Srv().Store().System().GetByName(model.SystemHostedPurchaseNeedsScreening)
|
||||
if err == nil && systemValue != nil {
|
||||
c.Err = model.NewAppError(where, "api.server.hosted_signup_unavailable.error", nil, "", http.StatusTooEarly)
|
||||
return
|
||||
}
|
||||
|
||||
ReturnStatusOK(w)
|
||||
}
|
||||
|
||||
func selfHostedInvoices(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
const where = "Api4.selfHostedInvoices"
|
||||
ensureSelfHostedAdmin(c, where)
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
invoices, err := c.App.Cloud().GetSelfHostedInvoices(c.AppContext)
|
||||
|
||||
if err != nil {
|
||||
if err.Error() == "404" {
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusNotFound).Wrap(errors.New("invoices for license not found"))
|
||||
return
|
||||
}
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
json, err := json.Marshal(invoices)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(json)
|
||||
}
|
||||
|
||||
func selfHostedInvoicePDF(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
const where = "Api4.selfHostedInvoicePDF"
|
||||
ensureSelfHostedAdmin(c, where)
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
pdfData, filename, appErr := c.App.Cloud().GetSelfHostedInvoicePDF(c.Params.InvoiceId)
|
||||
if appErr != nil {
|
||||
c.Err = model.NewAppError("Api4.getSubscriptionInvoicePDF", "api.cloud.request_error", nil, "", http.StatusInternalServerError).Wrap(appErr)
|
||||
return
|
||||
}
|
||||
|
||||
web.WriteFileResponse(
|
||||
filename,
|
||||
"application/pdf",
|
||||
int64(binary.Size(pdfData)),
|
||||
time.Now(),
|
||||
*c.App.Config().ServiceSettings.WebserverMode,
|
||||
bytes.NewReader(pdfData),
|
||||
false,
|
||||
w,
|
||||
r,
|
||||
)
|
||||
c.Err = model.NewAppError(where, "api.server.hosted_signup_unavailable.error", nil, "", http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
func handleSubscribeToNewsletter(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
@@ -326,80 +53,3 @@ func handleSubscribeToNewsletter(c *Context, w http.ResponseWriter, r *http.Requ
|
||||
|
||||
ReturnStatusOK(w)
|
||||
}
|
||||
|
||||
func selfHostedConfirmExpand(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
const where = "Api4.selfHostedConfirmExpand"
|
||||
|
||||
ensureSelfHostedAdmin(c, where)
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !checkSelfHostedPurchaseEnabled(c) {
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
|
||||
bodyBytes, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
var confirm model.SelfHostedConfirmPaymentMethodRequest
|
||||
err = json.Unmarshal(bodyBytes, &confirm)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError(where, "api.cloud.request_error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
user, userErr := c.App.GetUser(c.AppContext.Session().UserId)
|
||||
if userErr != nil {
|
||||
c.Err = userErr
|
||||
return
|
||||
}
|
||||
|
||||
confirmResponse, err := c.App.Cloud().ConfirmSelfHostedExpansion(confirm, user.Email)
|
||||
if err != nil {
|
||||
if confirmResponse != nil {
|
||||
c.App.NotifySelfHostedSignupProgress(confirmResponse.Progress, user.Id)
|
||||
}
|
||||
|
||||
if err.Error() == strconv.Itoa(http.StatusUnprocessableEntity) {
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusUnprocessableEntity).Wrap(err)
|
||||
return
|
||||
}
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
license, appErr := c.App.Srv().Platform().SaveLicense([]byte(confirmResponse.License))
|
||||
// dealing with an AppError
|
||||
if appErr != nil {
|
||||
if confirmResponse != nil {
|
||||
c.App.NotifySelfHostedSignupProgress(confirmResponse.Progress, user.Id)
|
||||
}
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
clientResponse, err := json.Marshal(model.SelfHostedSignupConfirmClientResponse{
|
||||
License: utils.GetClientLicense(license),
|
||||
Progress: confirmResponse.Progress,
|
||||
})
|
||||
if err != nil {
|
||||
if confirmResponse != nil {
|
||||
c.App.NotifySelfHostedSignupProgress(confirmResponse.Progress, user.Id)
|
||||
}
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
err := c.App.Cloud().ConfirmSelfHostedSignupLicenseApplication()
|
||||
if err != nil {
|
||||
c.Logger.Warn("Unable to confirm license application", mlog.Err(err))
|
||||
}
|
||||
}()
|
||||
|
||||
_, _ = w.Write(clientResponse)
|
||||
}
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package api4
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/v8/einterfaces/mocks"
|
||||
)
|
||||
|
||||
var valFalse = false
|
||||
var valTrue = true
|
||||
|
||||
func TestSelfHostedBootstrap(t *testing.T) {
|
||||
t.Run("feature flag off returns not implemented", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
cloud := mocks.CloudInterface{}
|
||||
|
||||
cloudImpl := th.App.Srv().Cloud
|
||||
defer func() {
|
||||
th.App.Srv().Cloud = cloudImpl
|
||||
}()
|
||||
th.App.Srv().Cloud = &cloud
|
||||
|
||||
th.Client.Login(context.Background(), th.SystemAdminUser.Email, th.SystemAdminUser.Password)
|
||||
|
||||
os.Setenv("MM_SERVICESETTINGS_SELFHOSTEDFIRSTTIMEPURCHASE", "false")
|
||||
defer os.Unsetenv("MM_SERVICESETTINGS_SELFHOSTEDFIRSTTIMEPURCHASE")
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.SelfHostedPurchase = &valFalse })
|
||||
th.App.ReloadConfig()
|
||||
|
||||
_, r, err := th.Client.BootstrapSelfHostedSignup(context.Background(), model.BootstrapSelfHostedSignupRequest{Email: th.SystemAdminUser.Email})
|
||||
|
||||
require.Equal(t, http.StatusNotImplemented, r.StatusCode)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("cloud instances not allowed to bootstrap self-hosted signup", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
cloud := mocks.CloudInterface{}
|
||||
|
||||
cloudImpl := th.App.Srv().Cloud
|
||||
defer func() {
|
||||
th.App.Srv().Cloud = cloudImpl
|
||||
}()
|
||||
th.App.Srv().Cloud = &cloud
|
||||
|
||||
th.Client.Login(context.Background(), th.SystemAdminUser.Email, th.SystemAdminUser.Password)
|
||||
|
||||
th.App.Srv().SetLicense(model.NewTestLicense("cloud"))
|
||||
os.Setenv("MM_SERVICESETTINGS_SELFHOSTEDFIRSTTIMEPURCHASE", "true")
|
||||
defer os.Unsetenv("MM_SERVICESETTINGS_SELFHOSTEDFIRSTTIMEPURCHASE")
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.SelfHostedPurchase = &valTrue })
|
||||
th.App.ReloadConfig()
|
||||
|
||||
_, r, err := th.Client.BootstrapSelfHostedSignup(context.Background(), model.BootstrapSelfHostedSignupRequest{Email: th.SystemAdminUser.Email})
|
||||
|
||||
require.Equal(t, http.StatusBadRequest, r.StatusCode)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("non-admins not allowed to bootstrap self-hosted signup", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
cloud := mocks.CloudInterface{}
|
||||
|
||||
cloudImpl := th.App.Srv().Cloud
|
||||
defer func() {
|
||||
th.App.Srv().Cloud = cloudImpl
|
||||
}()
|
||||
th.App.Srv().Cloud = &cloud
|
||||
|
||||
th.Client.Login(context.Background(), th.BasicUser.Email, th.BasicUser.Password)
|
||||
|
||||
os.Setenv("MM_SERVICESETTINGS_SELFHOSTEDFIRSTTIMEPURCHASE", "true")
|
||||
defer os.Unsetenv("MM_SERVICESETTINGS_SELFHOSTEDFIRSTTIMEPURCHASE")
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.SelfHostedPurchase = &valTrue })
|
||||
th.App.ReloadConfig()
|
||||
|
||||
_, r, err := th.Client.BootstrapSelfHostedSignup(context.Background(), model.BootstrapSelfHostedSignupRequest{Email: th.SystemAdminUser.Email})
|
||||
|
||||
require.Equal(t, http.StatusForbidden, r.StatusCode)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("self-hosted admins can bootstrap self-hosted signup", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.Client.Login(context.Background(), th.SystemAdminUser.Email, th.SystemAdminUser.Password)
|
||||
|
||||
os.Setenv("MM_SERVICESETTINGS_SELFHOSTEDFIRSTTIMEPURCHASE", "true")
|
||||
defer os.Unsetenv("MM_SERVICESETTINGS_SELFHOSTEDFIRSTTIMEPURCHASE")
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.SelfHostedPurchase = &valTrue })
|
||||
th.App.ReloadConfig()
|
||||
cloud := mocks.CloudInterface{}
|
||||
|
||||
cloud.Mock.On("BootstrapSelfHostedSignup", mock.Anything).Return(&model.BootstrapSelfHostedSignupResponse{Progress: "START"}, nil)
|
||||
|
||||
cloudImpl := th.App.Srv().Cloud
|
||||
defer func() {
|
||||
th.App.Srv().Cloud = cloudImpl
|
||||
}()
|
||||
th.App.Srv().Cloud = &cloud
|
||||
|
||||
response, r, err := th.Client.BootstrapSelfHostedSignup(context.Background(), model.BootstrapSelfHostedSignupRequest{Email: th.SystemAdminUser.Email})
|
||||
|
||||
require.Equal(t, http.StatusOK, r.StatusCode)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "START", response.Progress)
|
||||
})
|
||||
|
||||
t.Run("team edition returns bad request instead of panicking", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
cloudImpl := th.App.Srv().Cloud
|
||||
defer func() {
|
||||
th.App.Srv().Cloud = cloudImpl
|
||||
}()
|
||||
th.App.Srv().Cloud = nil
|
||||
|
||||
th.Client.Login(context.Background(), th.SystemAdminUser.Email, th.SystemAdminUser.Password)
|
||||
|
||||
os.Setenv("MM_SERVICESETTINGS_SELFHOSTEDFIRSTTIMEPURCHASE", "true")
|
||||
defer os.Unsetenv("MM_SERVICESETTINGS_SELFHOSTEDFIRSTTIMEPURCHASE")
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.SelfHostedPurchase = &valTrue })
|
||||
th.App.ReloadConfig()
|
||||
|
||||
_, r, err := th.Client.BootstrapSelfHostedSignup(context.Background(), model.BootstrapSelfHostedSignupRequest{Email: th.SystemAdminUser.Email})
|
||||
|
||||
require.Equal(t, http.StatusBadRequest, r.StatusCode)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
Ссылка в новой задаче
Block a user