From 33a922f026450dd04e6b7b9364676f981a0fa886 Mon Sep 17 00:00:00 2001 From: Yang Luo Date: Sat, 12 Aug 2023 12:52:53 +0800 Subject: [PATCH] Add custom HTTP SMS provider --- controllers/service.go | 10 +- object/sms.go | 2 + object/sms_custom.go | 75 ++++++++++++++ web/src/ProviderEditPage.js | 195 +++++++++++++++++++++++------------- web/src/Setting.js | 5 + 5 files changed, 214 insertions(+), 73 deletions(-) create mode 100644 object/sms_custom.go diff --git a/controllers/service.go b/controllers/service.go index 038feac4..537e4398 100644 --- a/controllers/service.go +++ b/controllers/service.go @@ -140,10 +140,12 @@ func (c *ApiController) SendSms() { return } - invalidReceivers := getInvalidSmsReceivers(smsForm) - if len(invalidReceivers) != 0 { - c.ResponseError(fmt.Sprintf(c.T("service:Invalid phone receivers: %s"), strings.Join(invalidReceivers, ", "))) - return + if provider.Type != "Custom HTTP SMS" { + invalidReceivers := getInvalidSmsReceivers(smsForm) + if len(invalidReceivers) != 0 { + c.ResponseError(fmt.Sprintf(c.T("service:Invalid phone receivers: %s"), strings.Join(invalidReceivers, ", "))) + return + } } err = object.SendSms(provider, smsForm.Content, smsForm.Receivers...) diff --git a/object/sms.go b/object/sms.go index 85f3d33b..1e7acada 100644 --- a/object/sms.go +++ b/object/sms.go @@ -26,6 +26,8 @@ func getSmsClient(provider *Provider) (sender.SmsClient, error) { if provider.Type == sender.HuaweiCloud || provider.Type == sender.AzureACS { client, err = sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.ProviderUrl, provider.AppId) + } else if provider.Type == "Custom HTTP SMS" { + client, err = newHttpSmsClient(provider.Endpoint, provider.Method, provider.ClientId, provider.Title) } else { client, err = sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.AppId) } diff --git a/object/sms_custom.go b/object/sms_custom.go new file mode 100644 index 00000000..c7bd6d45 --- /dev/null +++ b/object/sms_custom.go @@ -0,0 +1,75 @@ +// Copyright 2023 The Casdoor Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package object + +import ( + "bytes" + "fmt" + "net/http" + + "github.com/casdoor/casdoor/proxy" +) + +type HttpSmsClient struct { + endpoint string + method string + paramName string + text string +} + +func newHttpSmsClient(endpoint string, method string, paramName string, text string) (*HttpSmsClient, error) { + client := &HttpSmsClient{ + endpoint: endpoint, + method: method, + paramName: paramName, + text: text, + } + return client, nil +} + +func (c *HttpSmsClient) SendMessage(param map[string]string, targetPhoneNumber ...string) error { + var err error + + content := param["code"] + httpClient := proxy.DefaultHttpClient + + req, err := http.NewRequest(c.method, c.endpoint, bytes.NewBufferString(content)) + if err != nil { + return err + } + + if c.method == "POST" { + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.PostForm = map[string][]string{ + c.paramName: {content}, + } + } else if c.method == "GET" { + q := req.URL.Query() + q.Add(c.paramName, content) + req.URL.RawQuery = q.Encode() + } + + resp, err := httpClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("SendMessage() error, custom HTTP SMS request failed with status: %s", resp.Status) + } + + return err +} diff --git a/web/src/ProviderEditPage.js b/web/src/ProviderEditPage.js index 66cb620d..720e8436 100644 --- a/web/src/ProviderEditPage.js +++ b/web/src/ProviderEditPage.js @@ -356,7 +356,7 @@ class ProviderEditPage extends React.Component { { - this.updateProviderField("clientId", e.target.value); - }} /> - - - - - {this.getClientSecretLabel(this.state.provider)} : - - - { - this.updateProviderField("clientSecret", e.target.value); - }} /> - - - - ) + (this.state.provider.category === "Captcha" && this.state.provider.type === "Default") || + (this.state.provider.category === "SMS" && this.state.provider.type === "Custom HTTP SMS") || + (this.state.provider.category === "Web3") || + (this.state.provider.category === "Storage" && this.state.provider.type === "Local File System") ? null : ( + + + + {this.getClientIdLabel(this.state.provider)} : + + + { + this.updateProviderField("clientId", e.target.value); + }} /> + + + + + {this.getClientSecretLabel(this.state.provider)} : + + + { + this.updateProviderField("clientSecret", e.target.value); + }} /> + + + + ) } { this.state.provider.category !== "Email" && this.state.provider.type !== "WeChat" && this.state.provider.type !== "Aliyun Captcha" && this.state.provider.type !== "WeChat Pay" ? null : ( @@ -623,14 +630,14 @@ class ProviderEditPage extends React.Component { {Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} : - { + } value={this.state.provider.domain} onChange={e => { this.updateProviderField("domain", e.target.value); }} /> ) } - {this.state.provider.category === "Storage" ? ( + {this.state.provider.category === "Storage" || this.state.provider.type === "Custom HTTP SMS" ? (
{["Local File System"].includes(this.state.provider.type) ? null : ( @@ -638,25 +645,25 @@ class ProviderEditPage extends React.Component { {Setting.getLabel(i18next.t("provider:Endpoint"), i18next.t("provider:Region endpoint for Internet"))} : - { + } value={this.state.provider.endpoint} onChange={e => { this.updateProviderField("endpoint", e.target.value); }} /> )} - {["Local File System", "MinIO", "Tencent Cloud COS", "Google Cloud Storage", "Qiniu Cloud Kodo"].includes(this.state.provider.type) ? null : ( + {["Custom HTTP SMS", "Local File System", "MinIO", "Tencent Cloud COS", "Google Cloud Storage", "Qiniu Cloud Kodo"].includes(this.state.provider.type) ? null : ( {Setting.getLabel(i18next.t("provider:Endpoint (Intranet)"), i18next.t("provider:Region endpoint for Intranet"))} : - { + } value={this.state.provider.intranetEndpoint} onChange={e => { this.updateProviderField("intranetEndpoint", e.target.value); }} /> )} - {["Local File System"].includes(this.state.provider.type) ? null : ( + {["Custom HTTP SMS", "Local File System"].includes(this.state.provider.type) ? null : ( {Setting.getLabel(i18next.t("provider:Bucket"), i18next.t("provider:Bucket - Tooltip"))} : @@ -668,23 +675,25 @@ class ProviderEditPage extends React.Component { )} - - - {Setting.getLabel(i18next.t("provider:Path prefix"), i18next.t("provider:Path prefix - Tooltip"))} : - - - { - this.updateProviderField("pathPrefix", e.target.value); - }} /> - - - {["MinIO", "Google Cloud Storage", "Qiniu Cloud Kodo"].includes(this.state.provider.type) ? null : ( + {["Custom HTTP SMS"].includes(this.state.provider.type) ? null : ( + + + {Setting.getLabel(i18next.t("provider:Path prefix"), i18next.t("provider:Path prefix - Tooltip"))} : + + + { + this.updateProviderField("pathPrefix", e.target.value); + }} /> + + + )} + {["Custom HTTP SMS", "MinIO", "Google Cloud Storage", "Qiniu Cloud Kodo"].includes(this.state.provider.type) ? null : ( {Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} : - { + } value={this.state.provider.domain} disabled={this.state.provider.type === "Local File System"} onChange={e => { this.updateProviderField("domain", e.target.value); }} /> @@ -704,6 +713,49 @@ class ProviderEditPage extends React.Component { ) : null}
) : null} + { + this.state.provider.type !== "Custom HTTP SMS" ? null : ( + + + + {Setting.getLabel(i18next.t("general:Method"), i18next.t("provider:Method - Tooltip"))} : + + + + + + + + {Setting.getLabel(i18next.t("provider:Parameter name"), i18next.t("provider:Parameter name - Tooltip"))} : + + + { + this.updateProviderField("clientId", e.target.value); + }} /> + + + + + {Setting.getLabel(i18next.t("provider:Content"), i18next.t("provider:Content - Tooltip"))} : + + +