mirror of
https://github.com/casdoor/casdoor.git
synced 2025-05-23 02:35:49 +08:00
feat: can specify content type and http body field mapping for Custom HTTP Email provider (#3730)
This commit is contained in:
parent
2f4180b1b6
commit
0860cbf343
@ -15,6 +15,8 @@
|
|||||||
package email
|
package email
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -27,13 +29,21 @@ type HttpEmailProvider struct {
|
|||||||
endpoint string
|
endpoint string
|
||||||
method string
|
method string
|
||||||
httpHeaders map[string]string
|
httpHeaders map[string]string
|
||||||
|
bodyMapping map[string]string
|
||||||
|
contentType string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHttpEmailProvider(endpoint string, method string, httpHeaders map[string]string) *HttpEmailProvider {
|
func NewHttpEmailProvider(endpoint string, method string, httpHeaders map[string]string, bodyMapping map[string]string, contentType string) *HttpEmailProvider {
|
||||||
|
if contentType == "" {
|
||||||
|
contentType = "application/x-www-form-urlencoded"
|
||||||
|
}
|
||||||
|
|
||||||
client := &HttpEmailProvider{
|
client := &HttpEmailProvider{
|
||||||
endpoint: endpoint,
|
endpoint: endpoint,
|
||||||
method: method,
|
method: method,
|
||||||
httpHeaders: httpHeaders,
|
httpHeaders: httpHeaders,
|
||||||
|
bodyMapping: bodyMapping,
|
||||||
|
contentType: contentType,
|
||||||
}
|
}
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
@ -41,18 +51,52 @@ func NewHttpEmailProvider(endpoint string, method string, httpHeaders map[string
|
|||||||
func (c *HttpEmailProvider) Send(fromAddress string, fromName string, toAddress string, subject string, content string) error {
|
func (c *HttpEmailProvider) Send(fromAddress string, fromName string, toAddress string, subject string, content string) error {
|
||||||
var req *http.Request
|
var req *http.Request
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
fromNameField := "fromName"
|
||||||
|
toAddressField := "toAddress"
|
||||||
|
subjectField := "subject"
|
||||||
|
contentField := "content"
|
||||||
|
|
||||||
|
for k, v := range c.bodyMapping {
|
||||||
|
switch k {
|
||||||
|
case "fromName":
|
||||||
|
fromNameField = v
|
||||||
|
case "toAddress":
|
||||||
|
toAddressField = v
|
||||||
|
case "subject":
|
||||||
|
subjectField = v
|
||||||
|
case "content":
|
||||||
|
contentField = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if c.method == "POST" || c.method == "PUT" || c.method == "DELETE" {
|
if c.method == "POST" || c.method == "PUT" || c.method == "DELETE" {
|
||||||
|
bodyMap := make(map[string]string)
|
||||||
|
bodyMap[fromNameField] = fromName
|
||||||
|
bodyMap[toAddressField] = toAddress
|
||||||
|
bodyMap[subjectField] = subject
|
||||||
|
bodyMap[contentField] = content
|
||||||
|
|
||||||
|
var fromValueBytes []byte
|
||||||
|
if c.contentType == "application/json" {
|
||||||
|
fromValueBytes, err = json.Marshal(bodyMap)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req, err = http.NewRequest(c.method, c.endpoint, bytes.NewBuffer(fromValueBytes))
|
||||||
|
} else {
|
||||||
formValues := url.Values{}
|
formValues := url.Values{}
|
||||||
formValues.Set("fromName", fromName)
|
for k, v := range bodyMap {
|
||||||
formValues.Set("toAddress", toAddress)
|
formValues.Add(k, v)
|
||||||
formValues.Set("subject", subject)
|
}
|
||||||
formValues.Set("content", content)
|
|
||||||
req, err = http.NewRequest(c.method, c.endpoint, strings.NewReader(formValues.Encode()))
|
req, err = http.NewRequest(c.method, c.endpoint, strings.NewReader(formValues.Encode()))
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
req.Header.Set("Content-Type", c.contentType)
|
||||||
} else if c.method == "GET" {
|
} else if c.method == "GET" {
|
||||||
req, err = http.NewRequest(c.method, c.endpoint, nil)
|
req, err = http.NewRequest(c.method, c.endpoint, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -60,10 +104,10 @@ func (c *HttpEmailProvider) Send(fromAddress string, fromName string, toAddress
|
|||||||
}
|
}
|
||||||
|
|
||||||
q := req.URL.Query()
|
q := req.URL.Query()
|
||||||
q.Add("fromName", fromName)
|
q.Add(fromNameField, fromName)
|
||||||
q.Add("toAddress", toAddress)
|
q.Add(toAddressField, toAddress)
|
||||||
q.Add("subject", subject)
|
q.Add(subjectField, subject)
|
||||||
q.Add("content", content)
|
q.Add(contentField, content)
|
||||||
req.URL.RawQuery = q.Encode()
|
req.URL.RawQuery = q.Encode()
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("HttpEmailProvider's Send() error, unsupported method: %s", c.method)
|
return fmt.Errorf("HttpEmailProvider's Send() error, unsupported method: %s", c.method)
|
||||||
|
@ -18,11 +18,11 @@ type EmailProvider interface {
|
|||||||
Send(fromAddress string, fromName, toAddress string, subject string, content string) error
|
Send(fromAddress string, fromName, toAddress string, subject string, content string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetEmailProvider(typ string, clientId string, clientSecret string, host string, port int, disableSsl bool, endpoint string, method string, httpHeaders map[string]string) EmailProvider {
|
func GetEmailProvider(typ string, clientId string, clientSecret string, host string, port int, disableSsl bool, endpoint string, method string, httpHeaders map[string]string, bodyMapping map[string]string, contentType string) EmailProvider {
|
||||||
if typ == "Azure ACS" {
|
if typ == "Azure ACS" {
|
||||||
return NewAzureACSEmailProvider(clientSecret, host)
|
return NewAzureACSEmailProvider(clientSecret, host)
|
||||||
} else if typ == "Custom HTTP Email" {
|
} else if typ == "Custom HTTP Email" {
|
||||||
return NewHttpEmailProvider(endpoint, method, httpHeaders)
|
return NewHttpEmailProvider(endpoint, method, httpHeaders, bodyMapping, contentType)
|
||||||
} else if typ == "SendGrid" {
|
} else if typ == "SendGrid" {
|
||||||
return NewSendgridEmailProvider(clientSecret, host, endpoint)
|
return NewSendgridEmailProvider(clientSecret, host, endpoint)
|
||||||
} else {
|
} else {
|
||||||
|
@ -31,7 +31,7 @@ func TestSmtpServer(provider *Provider) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func SendEmail(provider *Provider, title string, content string, dest string, sender string) error {
|
func SendEmail(provider *Provider, title string, content string, dest string, sender string) error {
|
||||||
emailProvider := email.GetEmailProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, provider.Port, provider.DisableSsl, provider.Endpoint, provider.Method, provider.HttpHeaders)
|
emailProvider := email.GetEmailProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, provider.Port, provider.DisableSsl, provider.Endpoint, provider.Method, provider.HttpHeaders, provider.UserMapping, provider.IssuerUrl)
|
||||||
|
|
||||||
fromAddress := provider.ClientId2
|
fromAddress := provider.ClientId2
|
||||||
if fromAddress == "" {
|
if fromAddress == "" {
|
||||||
|
@ -42,6 +42,13 @@ const defaultUserMapping = {
|
|||||||
avatarUrl: "avatarUrl",
|
avatarUrl: "avatarUrl",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const defaultEmailMapping = {
|
||||||
|
fromName: "fromName",
|
||||||
|
toAddress: "toAddress",
|
||||||
|
subject: "subject",
|
||||||
|
content: "content",
|
||||||
|
};
|
||||||
|
|
||||||
class ProviderEditPage extends React.Component {
|
class ProviderEditPage extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
@ -72,7 +79,16 @@ class ProviderEditPage extends React.Component {
|
|||||||
|
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
const provider = res.data;
|
const provider = res.data;
|
||||||
|
if (provider.type === "Custom HTTP Email") {
|
||||||
|
if (!provider.userMapping) {
|
||||||
|
provider.userMapping = provider.userMapping || defaultEmailMapping;
|
||||||
|
}
|
||||||
|
if (!provider.userMapping?.fromName) {
|
||||||
|
provider.userMapping = defaultEmailMapping;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
provider.userMapping = provider.userMapping || defaultUserMapping;
|
provider.userMapping = provider.userMapping || defaultUserMapping;
|
||||||
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
provider: provider,
|
provider: provider,
|
||||||
});
|
});
|
||||||
@ -146,10 +162,17 @@ class ProviderEditPage extends React.Component {
|
|||||||
const requiredKeys = ["id", "username", "displayName"];
|
const requiredKeys = ["id", "username", "displayName"];
|
||||||
const provider = this.state.provider;
|
const provider = this.state.provider;
|
||||||
|
|
||||||
|
if (provider.type === "Custom HTTP Email") {
|
||||||
|
if (value === "") {
|
||||||
|
Setting.showMessage("error", i18next.t("provider:This field is required"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if (value === "" && requiredKeys.includes(key)) {
|
if (value === "" && requiredKeys.includes(key)) {
|
||||||
Setting.showMessage("error", i18next.t("provider:This field is required"));
|
Setting.showMessage("error", i18next.t("provider:This field is required"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
provider.userMapping[key] = value;
|
provider.userMapping[key] = value;
|
||||||
|
|
||||||
@ -184,6 +207,30 @@ class ProviderEditPage extends React.Component {
|
|||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderEmailMappingInput() {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{Setting.getLabel(i18next.t("provider:From name"), i18next.t("provider:From name - Tooltip"))} :
|
||||||
|
<Input value={this.state.provider.userMapping.fromName} onChange={e => {
|
||||||
|
this.updateUserMappingField("fromName", e.target.value);
|
||||||
|
}} />
|
||||||
|
{Setting.getLabel(i18next.t("provider:From address"), i18next.t("provider:From address - Tooltip"))} :
|
||||||
|
<Input value={this.state.provider.userMapping.toAddress} onChange={e => {
|
||||||
|
this.updateUserMappingField("toAddress", e.target.value);
|
||||||
|
}} />
|
||||||
|
{Setting.getLabel(i18next.t("provider:Subject"), i18next.t("provider:Subject - Tooltip"))} :
|
||||||
|
<Input value={this.state.provider.userMapping.subject} onChange={e => {
|
||||||
|
this.updateUserMappingField("subject", e.target.value);
|
||||||
|
}} />
|
||||||
|
{Setting.getLabel(i18next.t("provider:Email content"), i18next.t("provider:Email content - Tooltip"))} :
|
||||||
|
<Input value={this.state.provider.userMapping.content} onChange={e => {
|
||||||
|
this.updateUserMappingField("content", e.target.value);
|
||||||
|
}} />
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
getClientIdLabel(provider) {
|
getClientIdLabel(provider) {
|
||||||
switch (provider.category) {
|
switch (provider.category) {
|
||||||
case "OAuth":
|
case "OAuth":
|
||||||
@ -1097,6 +1144,9 @@ class ProviderEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
)}
|
)}
|
||||||
|
{
|
||||||
|
!["Custom HTTP Email"].includes(this.state.provider.type) ? null : (
|
||||||
|
<React.Fragment>
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={2}>
|
<Col style={{marginTop: "5px"}} span={2}>
|
||||||
{Setting.getLabel(i18next.t("general:Method"), i18next.t("provider:Method - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Method"), i18next.t("provider:Method - Tooltip"))} :
|
||||||
@ -1116,6 +1166,25 @@ class ProviderEditPage extends React.Component {
|
|||||||
</Select>
|
</Select>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
{
|
||||||
|
this.state.provider.method !== "GET" ? (<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("webhook:Content type"), i18next.t("webhook:Content type - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: "100%"}} value={this.state.provider.issuerUrl === "" ? "application/x-www-form-urlencoded" : this.state.provider.issuerUrl} onChange={value => {
|
||||||
|
this.updateProviderField("issuerUrl", value);
|
||||||
|
}}>
|
||||||
|
{
|
||||||
|
[
|
||||||
|
{id: "application/json", name: "application/json"},
|
||||||
|
{id: "application/x-www-form-urlencoded", name: "application/x-www-form-urlencoded"},
|
||||||
|
].map((method, index) => <Option key={index} value={method.id}>{method.name}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>) : null
|
||||||
|
}
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("provider:HTTP header"), i18next.t("provider:HTTP header - Tooltip"))} :
|
{Setting.getLabel(i18next.t("provider:HTTP header"), i18next.t("provider:HTTP header - Tooltip"))} :
|
||||||
@ -1124,6 +1193,17 @@ class ProviderEditPage extends React.Component {
|
|||||||
<HttpHeaderTable httpHeaders={this.state.provider.httpHeaders} onUpdateTable={(value) => {this.updateProviderField("httpHeaders", value);}} />
|
<HttpHeaderTable httpHeaders={this.state.provider.httpHeaders} onUpdateTable={(value) => {this.updateProviderField("httpHeaders", value);}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
{this.state.provider.method !== "GET" ? <Row style={{marginTop: "20px"}}>
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("provider:HTTP body mapping"), i18next.t("provider:HTTP body mapping - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22}>
|
||||||
|
{this.renderEmailMappingInput()}
|
||||||
|
</Col>
|
||||||
|
</Row> : null}
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("provider:Email title"), i18next.t("provider:Email title - Tooltip"))} :
|
{Setting.getLabel(i18next.t("provider:Email title"), i18next.t("provider:Email title - Tooltip"))} :
|
||||||
|
Loading…
x
Reference in New Issue
Block a user