mirror of
https://github.com/casdoor/casdoor.git
synced 2025-08-06 12:59:02 +08:00
Compare commits
36 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
eacc3fae5a | ||
![]() |
ce7a2e924b | ||
![]() |
ece060d03d | ||
![]() |
1276da4daa | ||
![]() |
616629ef99 | ||
![]() |
b633ecdcf2 | ||
![]() |
a12ba7fb85 | ||
![]() |
08a0092974 | ||
![]() |
bb04b10e8b | ||
![]() |
ea1414dfd0 | ||
![]() |
32a8a028d5 | ||
![]() |
0fe34c2f53 | ||
![]() |
dc57c476b7 | ||
![]() |
a7cb202ee9 | ||
![]() |
e5e264628e | ||
![]() |
8d4127f744 | ||
![]() |
1305899060 | ||
![]() |
411a85c7ab | ||
![]() |
f39358e122 | ||
![]() |
a84752bbb5 | ||
![]() |
e9d8ab8cdb | ||
![]() |
d12088e8e7 | ||
![]() |
c62588f9bc | ||
![]() |
16cd09d175 | ||
![]() |
7318ee6e3a | ||
![]() |
3459ef1479 | ||
![]() |
ca6b27f922 | ||
![]() |
e528e8883b | ||
![]() |
b7cd604e56 | ||
![]() |
3c2fd574a6 | ||
![]() |
a9de7d3aef | ||
![]() |
9820801634 | ||
![]() |
c6e422c3a8 | ||
![]() |
bc8e9cfd64 | ||
![]() |
c1eae9fcd8 | ||
![]() |
6dae6e4954 |
@@ -64,7 +64,6 @@ COPY --from=BACK /go/src/casdoor/docker-entrypoint.sh /docker-entrypoint.sh
|
|||||||
COPY --from=BACK /go/src/casdoor/conf/app.conf ./conf/app.conf
|
COPY --from=BACK /go/src/casdoor/conf/app.conf ./conf/app.conf
|
||||||
COPY --from=BACK /go/src/casdoor/version_info.txt ./go/src/casdoor/version_info.txt
|
COPY --from=BACK /go/src/casdoor/version_info.txt ./go/src/casdoor/version_info.txt
|
||||||
COPY --from=FRONT /web/build ./web/build
|
COPY --from=FRONT /web/build ./web/build
|
||||||
RUN mkdir tempFiles
|
|
||||||
|
|
||||||
ENTRYPOINT ["/bin/bash"]
|
ENTRYPOINT ["/bin/bash"]
|
||||||
CMD ["/docker-entrypoint.sh"]
|
CMD ["/docker-entrypoint.sh"]
|
||||||
|
@@ -88,6 +88,7 @@ p, *, *, *, /api/metrics, *, *
|
|||||||
p, *, *, GET, /api/get-pricing, *, *
|
p, *, *, GET, /api/get-pricing, *, *
|
||||||
p, *, *, GET, /api/get-plan, *, *
|
p, *, *, GET, /api/get-plan, *, *
|
||||||
p, *, *, GET, /api/get-subscription, *, *
|
p, *, *, GET, /api/get-subscription, *, *
|
||||||
|
p, *, *, GET, /api/get-provider, *, *
|
||||||
p, *, *, GET, /api/get-organization-names, *, *
|
p, *, *, GET, /api/get-organization-names, *, *
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -120,6 +121,10 @@ func IsAllowed(subOwner string, subName string, method string, urlPath string, o
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if subOwner == "app" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
if user != nil && user.IsAdmin && (subOwner == objOwner || (objOwner == "admin")) {
|
if user != nil && user.IsAdmin && (subOwner == objOwner || (objOwner == "admin")) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@@ -90,14 +90,24 @@ func (c *ApiController) GetApplication() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Input().Get("withKey") != "" && application.Cert != "" {
|
if c.Input().Get("withKey") != "" && application != nil && application.Cert != "" {
|
||||||
cert, err := object.GetCert(util.GetId(application.Owner, application.Cert))
|
cert, err := object.GetCert(util.GetId(application.Owner, application.Cert))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
application.CertPublicKey = cert.Certificate
|
if cert == nil {
|
||||||
|
cert, err = object.GetCert(util.GetId(application.Organization, application.Cert))
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cert != nil {
|
||||||
|
application.CertPublicKey = cert.Certificate
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.ResponseOk(object.GetMaskedApplication(application, userId))
|
c.ResponseOk(object.GetMaskedApplication(application, userId))
|
||||||
|
@@ -59,7 +59,7 @@ func tokenToResponse(token *object.Token) *Response {
|
|||||||
func (c *ApiController) HandleLoggedIn(application *object.Application, user *object.User, form *form.AuthForm) (resp *Response) {
|
func (c *ApiController) HandleLoggedIn(application *object.Application, user *object.User, form *form.AuthForm) (resp *Response) {
|
||||||
userId := user.GetId()
|
userId := user.GetId()
|
||||||
|
|
||||||
allowed, err := object.CheckAccessPermission(userId, application)
|
allowed, err := object.CheckLoginPermission(userId, application)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error(), nil)
|
c.ResponseError(err.Error(), nil)
|
||||||
return
|
return
|
||||||
|
@@ -176,11 +176,10 @@ func (c *ApiController) DeletePayment() {
|
|||||||
func (c *ApiController) NotifyPayment() {
|
func (c *ApiController) NotifyPayment() {
|
||||||
owner := c.Ctx.Input.Param(":owner")
|
owner := c.Ctx.Input.Param(":owner")
|
||||||
paymentName := c.Ctx.Input.Param(":payment")
|
paymentName := c.Ctx.Input.Param(":payment")
|
||||||
orderId := c.Ctx.Input.Param("order")
|
|
||||||
|
|
||||||
body := c.Ctx.Input.RequestBody
|
body := c.Ctx.Input.RequestBody
|
||||||
|
|
||||||
payment, err := object.NotifyPayment(c.Ctx.Request, body, owner, paymentName, orderId)
|
payment, err := object.NotifyPayment(body, owner, paymentName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
|
@@ -187,11 +187,11 @@ func (c *ApiController) BuyProduct() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
payUrl, orderId, err := object.BuyProduct(id, user, providerName, pricingName, planName, host)
|
payment, err := object.BuyProduct(id, user, providerName, pricingName, planName, host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.ResponseOk(payUrl, orderId)
|
c.ResponseOk(payment)
|
||||||
}
|
}
|
||||||
|
@@ -156,7 +156,7 @@ func (c *ApiController) DeleteToken() {
|
|||||||
// @Success 200 {object} object.TokenWrapper The Response object
|
// @Success 200 {object} object.TokenWrapper The Response object
|
||||||
// @Success 400 {object} object.TokenError The Response object
|
// @Success 400 {object} object.TokenError The Response object
|
||||||
// @Success 401 {object} object.TokenError The Response object
|
// @Success 401 {object} object.TokenError The Response object
|
||||||
// @router /login/oauth/access_token [post]
|
// @router api/login/oauth/access_token [post]
|
||||||
func (c *ApiController) GetOAuthToken() {
|
func (c *ApiController) GetOAuthToken() {
|
||||||
grantType := c.Input().Get("grant_type")
|
grantType := c.Input().Get("grant_type")
|
||||||
refreshToken := c.Input().Get("refresh_token")
|
refreshToken := c.Input().Get("refresh_token")
|
||||||
|
@@ -457,7 +457,16 @@ func (c *ApiController) SetPassword() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldPassword != "" {
|
isAdmin := c.IsAdmin()
|
||||||
|
if isAdmin {
|
||||||
|
if oldPassword != "" {
|
||||||
|
msg := object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
|
||||||
|
if msg != "" {
|
||||||
|
c.ResponseError(msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
msg := object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
|
msg := object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
|
||||||
if msg != "" {
|
if msg != "" {
|
||||||
c.ResponseError(msg)
|
c.ResponseError(msg)
|
||||||
|
227
email/azure_acs.go
Normal file
227
email/azure_acs.go
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
// 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 email
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
importanceNormal = "normal"
|
||||||
|
sendEmailEndpoint = "/emails:send"
|
||||||
|
apiVersion = "2023-03-31"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Email struct {
|
||||||
|
Recipients Recipients `json:"recipients"`
|
||||||
|
SenderAddress string `json:"senderAddress"`
|
||||||
|
Content Content `json:"content"`
|
||||||
|
Headers []CustomHeader `json:"headers"`
|
||||||
|
Tracking bool `json:"disableUserEngagementTracking"`
|
||||||
|
Importance string `json:"importance"`
|
||||||
|
ReplyTo []EmailAddress `json:"replyTo"`
|
||||||
|
Attachments []Attachment `json:"attachments"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Recipients struct {
|
||||||
|
To []EmailAddress `json:"to"`
|
||||||
|
CC []EmailAddress `json:"cc"`
|
||||||
|
BCC []EmailAddress `json:"bcc"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EmailAddress struct {
|
||||||
|
DisplayName string `json:"displayName"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Content struct {
|
||||||
|
Subject string `json:"subject"`
|
||||||
|
HTML string `json:"html"`
|
||||||
|
PlainText string `json:"plainText"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CustomHeader struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Attachment struct {
|
||||||
|
Content string `json:"contentBytesBase64"`
|
||||||
|
AttachmentType string `json:"attachmentType"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrorResponse struct {
|
||||||
|
Error CommunicationError `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommunicationError contains the error code and message
|
||||||
|
type CommunicationError struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AzureACSEmailProvider struct {
|
||||||
|
AccessKey string
|
||||||
|
Endpoint string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAzureACSEmailProvider(accessKey string, endpoint string) *AzureACSEmailProvider {
|
||||||
|
return &AzureACSEmailProvider{
|
||||||
|
AccessKey: accessKey,
|
||||||
|
Endpoint: endpoint,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEmail(fromAddress string, toAddress string, subject string, content string) *Email {
|
||||||
|
return &Email{
|
||||||
|
Recipients: Recipients{
|
||||||
|
To: []EmailAddress{
|
||||||
|
{
|
||||||
|
DisplayName: toAddress,
|
||||||
|
Address: toAddress,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SenderAddress: fromAddress,
|
||||||
|
Content: Content{
|
||||||
|
Subject: subject,
|
||||||
|
HTML: content,
|
||||||
|
},
|
||||||
|
Importance: importanceNormal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AzureACSEmailProvider) sendEmail(e *Email) error {
|
||||||
|
postBody, err := json.Marshal(e)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("email JSON marshall failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyBuffer := bytes.NewBuffer(postBody)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", a.Endpoint+sendEmailEndpoint+"?api-version="+apiVersion, bodyBuffer)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error creating AzureACS API request: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign the request using the AzureACS access key and HMAC-SHA256
|
||||||
|
err = signRequestHMAC(a.AccessKey, req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error signing AzureACS API request: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
// Some important header
|
||||||
|
req.Header.Set("repeatability-request-id", uuid.New().String())
|
||||||
|
req.Header.Set("repeatability-first-sent", time.Now().UTC().Format(http.TimeFormat))
|
||||||
|
|
||||||
|
// Send request
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error sending AzureACS API request: %s", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// Response error Handling
|
||||||
|
if resp.StatusCode == http.StatusBadRequest {
|
||||||
|
commError := ErrorResponse{}
|
||||||
|
|
||||||
|
err = json.NewDecoder(resp.Body).Decode(&commError)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("error sending email: %s", commError.Error.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusAccepted {
|
||||||
|
return fmt.Errorf("error sending email: status: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func signRequestHMAC(secret string, req *http.Request) error {
|
||||||
|
method := req.Method
|
||||||
|
host := req.URL.Host
|
||||||
|
pathAndQuery := req.URL.Path
|
||||||
|
|
||||||
|
if req.URL.RawQuery != "" {
|
||||||
|
pathAndQuery = pathAndQuery + "?" + req.URL.RawQuery
|
||||||
|
}
|
||||||
|
|
||||||
|
var content []byte
|
||||||
|
var err error
|
||||||
|
if req.Body != nil {
|
||||||
|
content, err = io.ReadAll(req.Body)
|
||||||
|
if err != nil {
|
||||||
|
// return err
|
||||||
|
content = []byte{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Body = io.NopCloser(bytes.NewBuffer(content))
|
||||||
|
|
||||||
|
key, err := base64.StdEncoding.DecodeString(secret)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error decoding secret: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp := time.Now().UTC().Format(http.TimeFormat)
|
||||||
|
contentHash := GetContentHashBase64(content)
|
||||||
|
stringToSign := fmt.Sprintf("%s\n%s\n%s;%s;%s", strings.ToUpper(method), pathAndQuery, timestamp, host, contentHash)
|
||||||
|
signature := GetHmac(stringToSign, key)
|
||||||
|
|
||||||
|
req.Header.Set("x-ms-content-sha256", contentHash)
|
||||||
|
req.Header.Set("x-ms-date", timestamp)
|
||||||
|
|
||||||
|
req.Header.Set("Authorization", "HMAC-SHA256 SignedHeaders=x-ms-date;host;x-ms-content-sha256&Signature="+signature)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetContentHashBase64(content []byte) string {
|
||||||
|
hasher := sha256.New()
|
||||||
|
hasher.Write(content)
|
||||||
|
|
||||||
|
return base64.StdEncoding.EncodeToString(hasher.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetHmac(content string, key []byte) string {
|
||||||
|
hmac := hmac.New(sha256.New, key)
|
||||||
|
hmac.Write([]byte(content))
|
||||||
|
|
||||||
|
return base64.StdEncoding.EncodeToString(hmac.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AzureACSEmailProvider) Send(fromAddress string, fromName string, toAddress string, subject string, content string) error {
|
||||||
|
e := newEmail(fromAddress, toAddress, subject, content)
|
||||||
|
|
||||||
|
return a.sendEmail(e)
|
||||||
|
}
|
27
email/provider.go
Normal file
27
email/provider.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// 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 email
|
||||||
|
|
||||||
|
type EmailProvider interface {
|
||||||
|
Send(fromAddress string, fromName, toAddress string, subject string, content string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetEmailProvider(typ string, clientId string, clientSecret string, appId string, host string, port int, disableSsl bool) EmailProvider {
|
||||||
|
if typ == "Azure ACS" {
|
||||||
|
return NewAzureACSEmailProvider(appId, host)
|
||||||
|
} else {
|
||||||
|
return NewSmtpEmailProvider(clientId, clientSecret, host, port, typ, disableSsl)
|
||||||
|
}
|
||||||
|
}
|
49
email/smtp.go
Normal file
49
email/smtp.go
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
// 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 email
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
|
||||||
|
"github.com/casdoor/gomail/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SmtpEmailProvider struct {
|
||||||
|
Dialer *gomail.Dialer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSmtpEmailProvider(userName string, password string, host string, port int, typ string, disableSsl bool) *SmtpEmailProvider {
|
||||||
|
dialer := &gomail.Dialer{}
|
||||||
|
dialer = gomail.NewDialer(host, port, userName, password)
|
||||||
|
if typ == "SUBMAIL" {
|
||||||
|
dialer.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
dialer.SSL = !disableSsl
|
||||||
|
|
||||||
|
return &SmtpEmailProvider{Dialer: dialer}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SmtpEmailProvider) Send(fromAddress string, fromName string, toAddress string, subject string, content string) error {
|
||||||
|
message := gomail.NewMessage()
|
||||||
|
|
||||||
|
message.SetAddressHeader("From", fromAddress, fromName)
|
||||||
|
message.SetHeader("To", toAddress)
|
||||||
|
message.SetHeader("Subject", subject)
|
||||||
|
message.SetBody("text/html", content)
|
||||||
|
|
||||||
|
message.SkipUsernameCheck = true
|
||||||
|
return s.Dialer.DialAndSend(message)
|
||||||
|
}
|
19
go.mod
19
go.mod
@@ -6,14 +6,14 @@ require (
|
|||||||
github.com/Masterminds/squirrel v1.5.3
|
github.com/Masterminds/squirrel v1.5.3
|
||||||
github.com/RobotsAndPencils/go-saml v0.0.0-20170520135329-fb13cb52a46b
|
github.com/RobotsAndPencils/go-saml v0.0.0-20170520135329-fb13cb52a46b
|
||||||
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387
|
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387
|
||||||
github.com/aliyun/alibaba-cloud-sdk-go v1.62.188 // indirect
|
github.com/aws/aws-sdk-go v1.45.5
|
||||||
github.com/aws/aws-sdk-go v1.44.4
|
|
||||||
github.com/beego/beego v1.12.12
|
github.com/beego/beego v1.12.12
|
||||||
github.com/beevik/etree v1.1.0
|
github.com/beevik/etree v1.1.0
|
||||||
github.com/casbin/casbin v1.9.1 // indirect
|
github.com/casbin/casbin v1.9.1 // indirect
|
||||||
github.com/casbin/casbin/v2 v2.30.1
|
github.com/casbin/casbin/v2 v2.37.0
|
||||||
github.com/casdoor/go-sms-sender v0.12.0
|
github.com/casdoor/go-sms-sender v0.14.0
|
||||||
github.com/casdoor/gomail/v2 v2.0.1
|
github.com/casdoor/gomail/v2 v2.0.1
|
||||||
|
github.com/casdoor/notify v0.43.0
|
||||||
github.com/casdoor/oss v1.3.0
|
github.com/casdoor/oss v1.3.0
|
||||||
github.com/casdoor/xorm-adapter/v3 v3.0.4
|
github.com/casdoor/xorm-adapter/v3 v3.0.4
|
||||||
github.com/casvisor/casvisor-go-sdk v1.0.3
|
github.com/casvisor/casvisor-go-sdk v1.0.3
|
||||||
@@ -30,14 +30,13 @@ require (
|
|||||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
|
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
|
||||||
github.com/go-webauthn/webauthn v0.6.0
|
github.com/go-webauthn/webauthn v0.6.0
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.1
|
||||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
||||||
github.com/lestrrat-go/jwx v1.2.21
|
github.com/lestrrat-go/jwx v1.2.21
|
||||||
github.com/lib/pq v1.10.9
|
github.com/lib/pq v1.10.9
|
||||||
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3
|
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3
|
||||||
github.com/markbates/goth v1.75.2
|
github.com/markbates/goth v1.75.2
|
||||||
github.com/mitchellh/mapstructure v1.5.0
|
github.com/mitchellh/mapstructure v1.5.0
|
||||||
github.com/nikoksr/notify v0.41.0
|
|
||||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
|
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
|
||||||
github.com/nyaruka/phonenumbers v1.1.5
|
github.com/nyaruka/phonenumbers v1.1.5
|
||||||
github.com/pquerna/otp v1.4.0
|
github.com/pquerna/otp v1.4.0
|
||||||
@@ -60,10 +59,12 @@ require (
|
|||||||
github.com/xorm-io/core v0.7.4
|
github.com/xorm-io/core v0.7.4
|
||||||
github.com/xorm-io/xorm v1.1.6
|
github.com/xorm-io/xorm v1.1.6
|
||||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||||
golang.org/x/crypto v0.11.0
|
golang.org/x/crypto v0.12.0
|
||||||
golang.org/x/net v0.13.0
|
golang.org/x/net v0.14.0
|
||||||
golang.org/x/oauth2 v0.10.0
|
golang.org/x/oauth2 v0.11.0
|
||||||
|
google.golang.org/api v0.138.0
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
gopkg.in/square/go-jose.v2 v2.6.0
|
gopkg.in/square/go-jose.v2 v2.6.0
|
||||||
|
maunium.net/go/mautrix v0.16.0
|
||||||
modernc.org/sqlite v1.18.2
|
modernc.org/sqlite v1.18.2
|
||||||
)
|
)
|
||||||
|
@@ -72,13 +72,13 @@ type FacebookCheckToken struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FacebookCheckTokenData
|
// FacebookCheckTokenData
|
||||||
// Get more detail via: https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow#checktoken
|
// Get more detail via: https://developers.facebook.com/docs/facebook-login/guides/advanced/manual-flow#checktoken
|
||||||
type FacebookCheckTokenData struct {
|
type FacebookCheckTokenData struct {
|
||||||
UserId string `json:"user_id"`
|
UserId string `json:"user_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetToken use code get access_token (*operation of getting code ought to be done in front)
|
// GetToken use code get access_token (*operation of getting code ought to be done in front)
|
||||||
// get more detail via: https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow#confirm
|
// get more detail via: https://developers.facebook.com/docs/facebook-login/guides/advanced/manual-flow#confirm
|
||||||
func (idp *FacebookIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
func (idp *FacebookIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||||
params := url.Values{}
|
params := url.Values{}
|
||||||
params.Add("client_id", idp.Config.ClientID)
|
params.Add("client_id", idp.Config.ClientID)
|
||||||
|
29
notification/bark.go
Normal file
29
notification/bark.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
// 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 notification
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/casdoor/notify"
|
||||||
|
"github.com/casdoor/notify/service/bark"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewBarkProvider(deviceKey string) (notify.Notifier, error) {
|
||||||
|
barkSrv := bark.New(deviceKey)
|
||||||
|
|
||||||
|
notifier := notify.New()
|
||||||
|
notifier.UseServices(barkSrv)
|
||||||
|
|
||||||
|
return notifier, nil
|
||||||
|
}
|
33
notification/dingtalk.go
Normal file
33
notification/dingtalk.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
// 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 notification
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/casdoor/notify"
|
||||||
|
"github.com/casdoor/notify/service/dingding"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewDingTalkProvider(token string, secret string) (notify.Notifier, error) {
|
||||||
|
cfg := dingding.Config{
|
||||||
|
Token: token,
|
||||||
|
Secret: secret,
|
||||||
|
}
|
||||||
|
dingtalkSrv := dingding.New(&cfg)
|
||||||
|
|
||||||
|
notifier := notify.New()
|
||||||
|
notifier.UseServices(dingtalkSrv)
|
||||||
|
|
||||||
|
return notifier, nil
|
||||||
|
}
|
37
notification/discord.go
Normal file
37
notification/discord.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
// 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 notification
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/casdoor/casdoor/proxy"
|
||||||
|
"github.com/casdoor/notify"
|
||||||
|
"github.com/casdoor/notify/service/discord"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewDiscordProvider(token string, channelId string) (*notify.Notify, error) {
|
||||||
|
discordSrv := discord.New()
|
||||||
|
|
||||||
|
err := discordSrv.AuthenticateWithBotToken(token)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
discordSrv.SetHttpClient(proxy.ProxyHttpClient)
|
||||||
|
discordSrv.AddReceivers(channelId)
|
||||||
|
|
||||||
|
notifier := notify.NewWithServices(discordSrv)
|
||||||
|
|
||||||
|
return notifier, nil
|
||||||
|
}
|
53
notification/google_chat.go
Normal file
53
notification/google_chat.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
// 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 notification
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/casdoor/notify"
|
||||||
|
"github.com/casdoor/notify/service/googlechat"
|
||||||
|
"google.golang.org/api/chat/v1"
|
||||||
|
"google.golang.org/api/option"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewGoogleChatProvider(credentials string) (*notify.Notify, error) {
|
||||||
|
withCred := option.WithCredentialsJSON([]byte(credentials))
|
||||||
|
withSpacesScope := option.WithScopes("https://www.googleapis.com/auth/chat.spaces")
|
||||||
|
|
||||||
|
listSvc, err := chat.NewService(context.Background(), withCred, withSpacesScope)
|
||||||
|
spaces, err := listSvc.Spaces.List().Do()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
receivers := make([]string, 0)
|
||||||
|
for _, space := range spaces.Spaces {
|
||||||
|
name := strings.Replace(space.Name, "spaces/", "", 1)
|
||||||
|
receivers = append(receivers, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
googleChatSrv, err := googlechat.New(withCred)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
googleChatSrv.AddReceivers(receivers...)
|
||||||
|
|
||||||
|
notifier := notify.NewWithServices(googleChatSrv)
|
||||||
|
|
||||||
|
return notifier, nil
|
||||||
|
}
|
29
notification/lark.go
Normal file
29
notification/lark.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
// 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 notification
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/casdoor/notify"
|
||||||
|
"github.com/casdoor/notify/service/lark"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewLarkProvider(webhookURL string) (notify.Notifier, error) {
|
||||||
|
larkSrv := lark.NewWebhookService(webhookURL)
|
||||||
|
|
||||||
|
notifier := notify.New()
|
||||||
|
notifier.UseServices(larkSrv)
|
||||||
|
|
||||||
|
return notifier, nil
|
||||||
|
}
|
32
notification/line.go
Normal file
32
notification/line.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
// 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 notification
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/casdoor/casdoor/proxy"
|
||||||
|
"github.com/casdoor/notify"
|
||||||
|
"github.com/casdoor/notify/service/line"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewLineProvider(channelSecret string, accessToken string, receiver string) (*notify.Notify, error) {
|
||||||
|
lineSrv, _ := line.NewWithHttpClient(channelSecret, accessToken, proxy.ProxyHttpClient)
|
||||||
|
|
||||||
|
lineSrv.AddReceivers(receiver)
|
||||||
|
|
||||||
|
notifier := notify.New()
|
||||||
|
notifier.UseServices(lineSrv)
|
||||||
|
|
||||||
|
return notifier, nil
|
||||||
|
}
|
36
notification/matrix.go
Normal file
36
notification/matrix.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
// 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 notification
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/casdoor/casdoor/proxy"
|
||||||
|
"github.com/casdoor/notify"
|
||||||
|
"github.com/casdoor/notify/service/matrix"
|
||||||
|
"maunium.net/go/mautrix/id"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewMatrixProvider(userId string, roomId string, accessToken string, homeServer string) (*notify.Notify, error) {
|
||||||
|
matrixSrv, err := matrix.New(id.UserID(userId), id.RoomID(roomId), homeServer, accessToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
matrixSrv.SetHttpClient(proxy.ProxyHttpClient)
|
||||||
|
|
||||||
|
notifier := notify.New()
|
||||||
|
notifier.UseServices(matrixSrv)
|
||||||
|
|
||||||
|
return notifier, nil
|
||||||
|
}
|
31
notification/microsoft_teams.go
Normal file
31
notification/microsoft_teams.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// 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 notification
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/casdoor/notify"
|
||||||
|
"github.com/casdoor/notify/service/msteams"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewMicrosoftTeamsProvider(webhookURL string) (notify.Notifier, error) {
|
||||||
|
msTeamsSrv := msteams.New()
|
||||||
|
|
||||||
|
msTeamsSrv.AddReceivers(webhookURL)
|
||||||
|
|
||||||
|
notifier := notify.New()
|
||||||
|
notifier.UseServices(msTeamsSrv)
|
||||||
|
|
||||||
|
return notifier, nil
|
||||||
|
}
|
@@ -14,13 +14,45 @@
|
|||||||
|
|
||||||
package notification
|
package notification
|
||||||
|
|
||||||
import "github.com/nikoksr/notify"
|
import "github.com/casdoor/notify"
|
||||||
|
|
||||||
func GetNotificationProvider(typ string, appId string, receiver string, method string, title string) (notify.Notifier, error) {
|
func GetNotificationProvider(typ string, clientId string, clientSecret string, clientId2 string, clientSecret2 string, appId string, receiver string, method string, title string, metaData string) (notify.Notifier, error) {
|
||||||
if typ == "Telegram" {
|
if typ == "Telegram" {
|
||||||
return NewTelegramProvider(appId, receiver)
|
return NewTelegramProvider(appId, receiver)
|
||||||
} else if typ == "Custom HTTP" {
|
} else if typ == "Custom HTTP" {
|
||||||
return NewCustomHttpProvider(receiver, method, title)
|
return NewCustomHttpProvider(receiver, method, title)
|
||||||
|
} else if typ == "DingTalk" {
|
||||||
|
return NewDingTalkProvider(appId, receiver)
|
||||||
|
} else if typ == "Lark" {
|
||||||
|
return NewLarkProvider(receiver)
|
||||||
|
} else if typ == "Microsoft Teams" {
|
||||||
|
return NewMicrosoftTeamsProvider(receiver)
|
||||||
|
} else if typ == "Bark" {
|
||||||
|
return NewBarkProvider(receiver)
|
||||||
|
} else if typ == "Pushover" {
|
||||||
|
return NewPushoverProvider(appId, receiver)
|
||||||
|
} else if typ == "Pushbullet" {
|
||||||
|
return NewPushbulletProvider(appId, receiver)
|
||||||
|
} else if typ == "Slack" {
|
||||||
|
return NewSlackProvider(appId, receiver)
|
||||||
|
} else if typ == "Webpush" {
|
||||||
|
return NewWebpushProvider(clientId, clientSecret, receiver)
|
||||||
|
} else if typ == "Discord" {
|
||||||
|
return NewDiscordProvider(appId, receiver)
|
||||||
|
} else if typ == "Google Chat" {
|
||||||
|
return NewGoogleChatProvider(metaData)
|
||||||
|
} else if typ == "Line" {
|
||||||
|
return NewLineProvider(clientSecret, appId, receiver)
|
||||||
|
} else if typ == "Matrix" {
|
||||||
|
return NewMatrixProvider(clientId, clientSecret, appId, receiver)
|
||||||
|
} else if typ == "Twitter" {
|
||||||
|
return NewTwitterProvider(clientId, clientSecret, clientId2, clientSecret2, receiver)
|
||||||
|
} else if typ == "Reddit" {
|
||||||
|
return NewRedditProvider(clientId, clientSecret, clientId2, clientSecret2, receiver)
|
||||||
|
} else if typ == "Rocket Chat" {
|
||||||
|
return NewRocketChatProvider(clientId, clientSecret, appId, receiver)
|
||||||
|
} else if typ == "Viber" {
|
||||||
|
return NewViberProvider(clientId, clientSecret, appId, receiver)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
31
notification/pushbullet.go
Normal file
31
notification/pushbullet.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// 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 notification
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/casdoor/notify"
|
||||||
|
"github.com/casdoor/notify/service/pushbullet"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewPushbulletProvider(apiToken string, deviceNickname string) (notify.Notifier, error) {
|
||||||
|
pushbulletSrv := pushbullet.New(apiToken)
|
||||||
|
|
||||||
|
pushbulletSrv.AddReceivers(deviceNickname)
|
||||||
|
|
||||||
|
notifier := notify.New()
|
||||||
|
notifier.UseServices(pushbulletSrv)
|
||||||
|
|
||||||
|
return notifier, nil
|
||||||
|
}
|
31
notification/pushover.go
Normal file
31
notification/pushover.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// 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 notification
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/casdoor/notify"
|
||||||
|
"github.com/casdoor/notify/service/pushover"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewPushoverProvider(appToken string, recipientID string) (notify.Notifier, error) {
|
||||||
|
pushoverSrv := pushover.New(appToken)
|
||||||
|
|
||||||
|
pushoverSrv.AddReceivers(recipientID)
|
||||||
|
|
||||||
|
notifier := notify.New()
|
||||||
|
notifier.UseServices(pushoverSrv)
|
||||||
|
|
||||||
|
return notifier, nil
|
||||||
|
}
|
34
notification/reddit.go
Normal file
34
notification/reddit.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
// 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 notification
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/casdoor/notify"
|
||||||
|
"github.com/casdoor/notify/service/reddit"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewRedditProvider(clientId string, clientSecret string, username string, password string, recipient string) (notify.Notifier, error) {
|
||||||
|
redditSrv, err := reddit.New(clientId, clientSecret, username, password)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
redditSrv.AddReceivers(recipient)
|
||||||
|
|
||||||
|
notifier := notify.New()
|
||||||
|
notifier.UseServices(redditSrv)
|
||||||
|
|
||||||
|
return notifier, nil
|
||||||
|
}
|
47
notification/rocket_chat.go
Normal file
47
notification/rocket_chat.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
// 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 notification
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/casdoor/notify"
|
||||||
|
"github.com/casdoor/notify/service/rocketchat"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewRocketChatProvider(clientId string, clientSecret string, endpoint string, channelName string) (notify.Notifier, error) {
|
||||||
|
parts := strings.Split(endpoint, "://")
|
||||||
|
|
||||||
|
var scheme, serverURL string
|
||||||
|
if len(parts) >= 2 {
|
||||||
|
scheme = parts[0]
|
||||||
|
serverURL = parts[1]
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("parse endpoint error")
|
||||||
|
}
|
||||||
|
|
||||||
|
rocketChatSrv, err := rocketchat.New(serverURL, scheme, clientId, clientSecret)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rocketChatSrv.AddReceivers(channelName)
|
||||||
|
|
||||||
|
notifier := notify.New()
|
||||||
|
notifier.UseServices(rocketChatSrv)
|
||||||
|
|
||||||
|
return notifier, nil
|
||||||
|
}
|
30
notification/slack.go
Normal file
30
notification/slack.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
// 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 notification
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/casdoor/notify"
|
||||||
|
"github.com/casdoor/notify/service/slack"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewSlackProvider(apiToken string, channelID string) (*notify.Notify, error) {
|
||||||
|
slackSrv := slack.New(apiToken)
|
||||||
|
slackSrv.AddReceivers(channelID)
|
||||||
|
|
||||||
|
notifier := notify.New()
|
||||||
|
notifier.UseServices(slackSrv)
|
||||||
|
|
||||||
|
return notifier, nil
|
||||||
|
}
|
@@ -18,9 +18,9 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/proxy"
|
"github.com/casdoor/casdoor/proxy"
|
||||||
|
"github.com/casdoor/notify"
|
||||||
|
"github.com/casdoor/notify/service/telegram"
|
||||||
api "github.com/go-telegram-bot-api/telegram-bot-api"
|
api "github.com/go-telegram-bot-api/telegram-bot-api"
|
||||||
"github.com/nikoksr/notify"
|
|
||||||
"github.com/nikoksr/notify/service/telegram"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewTelegramProvider(apiToken string, chatIdStr string) (notify.Notifier, error) {
|
func NewTelegramProvider(apiToken string, chatIdStr string) (notify.Notifier, error) {
|
||||||
@@ -28,15 +28,18 @@ func NewTelegramProvider(apiToken string, chatIdStr string) (notify.Notifier, er
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
t := &telegram.Telegram{}
|
telegramSrv := &telegram.Telegram{}
|
||||||
t.SetClient(client)
|
telegramSrv.SetClient(client)
|
||||||
|
|
||||||
chatId, err := strconv.ParseInt(chatIdStr, 10, 64)
|
chatId, err := strconv.ParseInt(chatIdStr, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
t.AddReceivers(chatId)
|
telegramSrv.AddReceivers(chatId)
|
||||||
|
|
||||||
return t, nil
|
notifier := notify.New()
|
||||||
|
notifier.UseServices(telegramSrv)
|
||||||
|
|
||||||
|
return notifier, nil
|
||||||
}
|
}
|
||||||
|
41
notification/twitter.go
Normal file
41
notification/twitter.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
// 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 notification
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/casdoor/casdoor/proxy"
|
||||||
|
"github.com/casdoor/notify"
|
||||||
|
"github.com/casdoor/notify/service/twitter"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewTwitterProvider(consumerKey string, consumerSecret string, accessToken string, accessTokenSecret string, twitterId string) (*notify.Notify, error) {
|
||||||
|
credentials := twitter.Credentials{
|
||||||
|
ConsumerKey: consumerKey,
|
||||||
|
ConsumerSecret: consumerSecret,
|
||||||
|
AccessToken: accessToken,
|
||||||
|
AccessTokenSecret: accessTokenSecret,
|
||||||
|
}
|
||||||
|
twitterSrv, err := twitter.NewWithHttpClient(credentials, proxy.ProxyHttpClient)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
twitterSrv.AddReceivers(twitterId)
|
||||||
|
|
||||||
|
notifier := notify.New()
|
||||||
|
notifier.UseServices(twitterSrv)
|
||||||
|
|
||||||
|
return notifier, nil
|
||||||
|
}
|
36
notification/viber.go
Normal file
36
notification/viber.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
// 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 notification
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/casdoor/notify"
|
||||||
|
"github.com/casdoor/notify/service/viber"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewViberProvider(senderName string, appKey string, webhookURL string, receiverId string) (notify.Notifier, error) {
|
||||||
|
viberSrv := viber.New(appKey, senderName, "")
|
||||||
|
|
||||||
|
err := viberSrv.SetWebhook(webhookURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
viberSrv.AddReceivers(receiverId)
|
||||||
|
|
||||||
|
notifier := notify.New()
|
||||||
|
notifier.UseServices(viberSrv)
|
||||||
|
|
||||||
|
return notifier, nil
|
||||||
|
}
|
33
notification/webpush.go
Normal file
33
notification/webpush.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
// 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 notification
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/casdoor/notify"
|
||||||
|
"github.com/casdoor/notify/service/webpush"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewWebpushProvider(publicKey string, privateKey string, endpoint string) (*notify.Notify, error) {
|
||||||
|
webpushSrv := webpush.New(publicKey, privateKey)
|
||||||
|
|
||||||
|
subscription := webpush.Subscription{
|
||||||
|
Endpoint: endpoint,
|
||||||
|
}
|
||||||
|
webpushSrv.AddReceivers(subscription)
|
||||||
|
|
||||||
|
notifier := notify.NewWithServices(webpushSrv)
|
||||||
|
|
||||||
|
return notifier, nil
|
||||||
|
}
|
@@ -428,15 +428,14 @@ func (application *Application) GetId() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (application *Application) IsRedirectUriValid(redirectUri string) bool {
|
func (application *Application) IsRedirectUriValid(redirectUri string) bool {
|
||||||
isValid := false
|
redirectUris := append([]string{"http://localhost:"}, application.RedirectUris...)
|
||||||
for _, targetUri := range application.RedirectUris {
|
for _, targetUri := range redirectUris {
|
||||||
targetUriRegex := regexp.MustCompile(targetUri)
|
targetUriRegex := regexp.MustCompile(targetUri)
|
||||||
if targetUriRegex.MatchString(redirectUri) || strings.Contains(redirectUri, targetUri) {
|
if targetUriRegex.MatchString(redirectUri) || strings.Contains(redirectUri, targetUri) {
|
||||||
isValid = true
|
return true
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return isValid
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsOriginAllowed(origin string) (bool, error) {
|
func IsOriginAllowed(origin string) (bool, error) {
|
||||||
|
@@ -33,10 +33,8 @@ type Cert struct {
|
|||||||
BitSize int `json:"bitSize"`
|
BitSize int `json:"bitSize"`
|
||||||
ExpireInYears int `json:"expireInYears"`
|
ExpireInYears int `json:"expireInYears"`
|
||||||
|
|
||||||
Certificate string `xorm:"mediumtext" json:"certificate"`
|
Certificate string `xorm:"mediumtext" json:"certificate"`
|
||||||
PrivateKey string `xorm:"mediumtext" json:"privateKey"`
|
PrivateKey string `xorm:"mediumtext" json:"privateKey"`
|
||||||
AuthorityPublicKey string `xorm:"mediumtext" json:"authorityPublicKey"`
|
|
||||||
AuthorityRootPublicKey string `xorm:"mediumtext" json:"authorityRootPublicKey"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetMaskedCert(cert *Cert) *Cert {
|
func GetMaskedCert(cert *Cert) *Cert {
|
||||||
|
BIN
object/cert.go~
Normal file
BIN
object/cert.go~
Normal file
Binary file not shown.
@@ -350,7 +350,7 @@ func CheckUserPermission(requestUserId, userId string, strict bool, lang string)
|
|||||||
return hasPermission, fmt.Errorf(i18n.Translate(lang, "auth:Unauthorized operation"))
|
return hasPermission, fmt.Errorf(i18n.Translate(lang, "auth:Unauthorized operation"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckAccessPermission(userId string, application *Application) (bool, error) {
|
func CheckLoginPermission(userId string, application *Application) (bool, error) {
|
||||||
var err error
|
var err error
|
||||||
if userId == "built-in/admin" {
|
if userId == "built-in/admin" {
|
||||||
return true, nil
|
return true, nil
|
||||||
@@ -361,32 +361,40 @@ func CheckAccessPermission(userId string, application *Application) (bool, error
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
allowed := true
|
allowCount := 0
|
||||||
|
denyCount := 0
|
||||||
for _, permission := range permissions {
|
for _, permission := range permissions {
|
||||||
if !permission.IsEnabled {
|
if !permission.IsEnabled || permission.ResourceType != "Application" || !permission.isResourceHit(application.Name) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
isHit := false
|
if permission.isUserHit(userId) {
|
||||||
for _, resource := range permission.Resources {
|
allowCount += 1
|
||||||
if application.Name == resource {
|
|
||||||
isHit = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if isHit {
|
enforcer := getPermissionEnforcer(permission)
|
||||||
containsAsterisk := ContainsAsterisk(userId, permission.Users)
|
|
||||||
if containsAsterisk {
|
var isAllowed bool
|
||||||
return true, err
|
isAllowed, err = enforcer.Enforce(userId, application.Name, "Read")
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if isAllowed {
|
||||||
|
if permission.Effect == "Allow" {
|
||||||
|
allowCount += 1
|
||||||
}
|
}
|
||||||
enforcer := getPermissionEnforcer(permission)
|
} else {
|
||||||
if allowed, err = enforcer.Enforce(userId, application.Name, "read"); allowed {
|
if permission.Effect == "Deny" {
|
||||||
return allowed, err
|
denyCount += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return allowed, err
|
|
||||||
|
if denyCount > 0 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckUsername(username string, lang string) string {
|
func CheckUsername(username string, lang string) string {
|
||||||
|
@@ -19,6 +19,7 @@ package object
|
|||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/email"
|
||||||
"github.com/casdoor/gomail/v2"
|
"github.com/casdoor/gomail/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -35,9 +36,7 @@ func getDialer(provider *Provider) *gomail.Dialer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
dialer := getDialer(provider)
|
emailProvider := email.GetEmailProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.AppId, provider.Host, provider.Port, provider.DisableSsl)
|
||||||
|
|
||||||
message := gomail.NewMessage()
|
|
||||||
|
|
||||||
fromAddress := provider.ClientId2
|
fromAddress := provider.ClientId2
|
||||||
if fromAddress == "" {
|
if fromAddress == "" {
|
||||||
@@ -49,14 +48,7 @@ func SendEmail(provider *Provider, title string, content string, dest string, se
|
|||||||
fromName = sender
|
fromName = sender
|
||||||
}
|
}
|
||||||
|
|
||||||
message.SetAddressHeader("From", fromAddress, fromName)
|
return emailProvider.Send(fromAddress, fromName, dest, title, content)
|
||||||
message.SetHeader("To", dest)
|
|
||||||
message.SetHeader("Subject", title)
|
|
||||||
message.SetBody("text/html", content)
|
|
||||||
|
|
||||||
message.SkipUsernameCheck = true
|
|
||||||
|
|
||||||
return dialer.DialAndSend(message)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DailSmtpServer Dail Smtp server
|
// DailSmtpServer Dail Smtp server
|
||||||
|
@@ -19,7 +19,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/beego/beego"
|
|
||||||
"github.com/beego/beego/context"
|
"github.com/beego/beego/context"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/pquerna/otp"
|
"github.com/pquerna/otp"
|
||||||
@@ -39,10 +38,11 @@ type TotpMfa struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (mfa *TotpMfa) Initiate(ctx *context.Context, userId string) (*MfaProps, error) {
|
func (mfa *TotpMfa) Initiate(ctx *context.Context, userId string) (*MfaProps, error) {
|
||||||
issuer := beego.AppConfig.String("appname")
|
//issuer := beego.AppConfig.String("appname")
|
||||||
if issuer == "" {
|
//if issuer == "" {
|
||||||
issuer = "casdoor"
|
// issuer = "casdoor"
|
||||||
}
|
//}
|
||||||
|
issuer := "casdoor"
|
||||||
|
|
||||||
key, err := totp.Generate(totp.GenerateOpts{
|
key, err := totp.Generate(totp.GenerateOpts{
|
||||||
Issuer: issuer,
|
Issuer: issuer,
|
||||||
@@ -81,12 +81,15 @@ func (mfa *TotpMfa) SetupVerify(ctx *context.Context, passcode string) error {
|
|||||||
return errors.New("totp secret is missing")
|
return errors.New("totp secret is missing")
|
||||||
}
|
}
|
||||||
|
|
||||||
result, _ := totp.ValidateCustom(passcode, secret.(string), time.Now().UTC(), totp.ValidateOpts{
|
result, err := totp.ValidateCustom(passcode, secret.(string), time.Now().UTC(), totp.ValidateOpts{
|
||||||
Period: MfaTotpPeriodInSeconds,
|
Period: MfaTotpPeriodInSeconds,
|
||||||
Skew: 1,
|
Skew: 1,
|
||||||
Digits: otp.DigitsSix,
|
Digits: otp.DigitsSix,
|
||||||
Algorithm: otp.AlgorithmSHA1,
|
Algorithm: otp.AlgorithmSHA1,
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if result {
|
if result {
|
||||||
return nil
|
return nil
|
||||||
@@ -125,7 +128,15 @@ func (mfa *TotpMfa) Enable(ctx *context.Context, user *User) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (mfa *TotpMfa) Verify(passcode string) error {
|
func (mfa *TotpMfa) Verify(passcode string) error {
|
||||||
result := totp.Validate(passcode, mfa.Config.Secret)
|
result, err := totp.ValidateCustom(passcode, mfa.Config.Secret, time.Now().UTC(), totp.ValidateOpts{
|
||||||
|
Period: MfaTotpPeriodInSeconds,
|
||||||
|
Skew: 1,
|
||||||
|
Digits: otp.DigitsSix,
|
||||||
|
Algorithm: otp.AlgorithmSHA1,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if result {
|
if result {
|
||||||
return nil
|
return nil
|
||||||
|
@@ -18,12 +18,12 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/notification"
|
"github.com/casdoor/casdoor/notification"
|
||||||
"github.com/nikoksr/notify"
|
"github.com/casdoor/notify"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getNotificationClient(provider *Provider) (notify.Notifier, error) {
|
func getNotificationClient(provider *Provider) (notify.Notifier, error) {
|
||||||
var client notify.Notifier
|
var client notify.Notifier
|
||||||
client, err := notification.GetNotificationProvider(provider.Type, provider.AppId, provider.Receiver, provider.Method, provider.Title)
|
client, err := notification.GetNotificationProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.ClientId2, provider.ClientSecret2, provider.AppId, provider.Receiver, provider.Method, provider.Title, provider.Metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@@ -16,7 +16,6 @@ package object
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/pp"
|
"github.com/casdoor/casdoor/pp"
|
||||||
|
|
||||||
@@ -55,6 +54,7 @@ type Payment struct {
|
|||||||
// Order Info
|
// Order Info
|
||||||
OutOrderId string `xorm:"varchar(100)" json:"outOrderId"`
|
OutOrderId string `xorm:"varchar(100)" json:"outOrderId"`
|
||||||
PayUrl string `xorm:"varchar(2000)" json:"payUrl"`
|
PayUrl string `xorm:"varchar(2000)" json:"payUrl"`
|
||||||
|
SuccessUrl string `xorm:"varchar(2000)" json:"successUrl""` // `successUrl` is redirected from `payUrl` after pay success
|
||||||
State pp.PaymentState `xorm:"varchar(100)" json:"state"`
|
State pp.PaymentState `xorm:"varchar(100)" json:"state"`
|
||||||
Message string `xorm:"varchar(2000)" json:"message"`
|
Message string `xorm:"varchar(2000)" json:"message"`
|
||||||
}
|
}
|
||||||
@@ -152,7 +152,7 @@ func DeletePayment(payment *Payment) (bool, error) {
|
|||||||
return affected != 0, nil
|
return affected != 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func notifyPayment(request *http.Request, body []byte, owner string, paymentName string, orderId string) (*Payment, *pp.NotifyResult, error) {
|
func notifyPayment(body []byte, owner string, paymentName string) (*Payment, *pp.NotifyResult, error) {
|
||||||
payment, err := getPayment(owner, paymentName)
|
payment, err := getPayment(owner, paymentName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
@@ -166,7 +166,7 @@ func notifyPayment(request *http.Request, body []byte, owner string, paymentName
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
pProvider, cert, err := provider.getPaymentProvider()
|
pProvider, err := GetPaymentProvider(provider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -180,11 +180,7 @@ func notifyPayment(request *http.Request, body []byte, owner string, paymentName
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if orderId == "" {
|
notifyResult, err := pProvider.Notify(body, payment.OutOrderId)
|
||||||
orderId = payment.OutOrderId
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyResult, err := pProvider.Notify(request, body, cert.AuthorityPublicKey, orderId)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return payment, nil, err
|
return payment, nil, err
|
||||||
}
|
}
|
||||||
@@ -205,8 +201,8 @@ func notifyPayment(request *http.Request, body []byte, owner string, paymentName
|
|||||||
return payment, notifyResult, nil
|
return payment, notifyResult, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NotifyPayment(request *http.Request, body []byte, owner string, paymentName string, orderId string) (*Payment, error) {
|
func NotifyPayment(body []byte, owner string, paymentName string) (*Payment, error) {
|
||||||
payment, notifyResult, err := notifyPayment(request, body, owner, paymentName, orderId)
|
payment, notifyResult, err := notifyPayment(body, owner, paymentName)
|
||||||
if payment != nil {
|
if payment != nil {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
payment.State = pp.PaymentStateError
|
payment.State = pp.PaymentStateError
|
||||||
@@ -234,7 +230,7 @@ func invoicePayment(payment *Payment) (string, error) {
|
|||||||
return "", fmt.Errorf("the payment provider: %s does not exist", payment.Provider)
|
return "", fmt.Errorf("the payment provider: %s does not exist", payment.Provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
pProvider, _, err := provider.getPaymentProvider()
|
pProvider, err := GetPaymentProvider(provider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@@ -30,6 +30,7 @@ type Permission struct {
|
|||||||
Description string `xorm:"varchar(100)" json:"description"`
|
Description string `xorm:"varchar(100)" json:"description"`
|
||||||
|
|
||||||
Users []string `xorm:"mediumtext" json:"users"`
|
Users []string `xorm:"mediumtext" json:"users"`
|
||||||
|
Groups []string `xorm:"mediumtext" json:"groups"`
|
||||||
Roles []string `xorm:"mediumtext" json:"roles"`
|
Roles []string `xorm:"mediumtext" json:"roles"`
|
||||||
Domains []string `xorm:"mediumtext" json:"domains"`
|
Domains []string `xorm:"mediumtext" json:"domains"`
|
||||||
|
|
||||||
@@ -60,10 +61,6 @@ type PermissionRule struct {
|
|||||||
|
|
||||||
const builtInAvailableField = 5 // Casdoor built-in adapter, use V5 to filter permission, so has 5 available field
|
const builtInAvailableField = 5 // Casdoor built-in adapter, use V5 to filter permission, so has 5 available field
|
||||||
|
|
||||||
func (p *Permission) GetId() string {
|
|
||||||
return util.GetId(p.Owner, p.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetPermissionCount(owner, field, value string) (int64, error) {
|
func GetPermissionCount(owner, field, value string) (int64, error) {
|
||||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||||
return session.Count(&Permission{})
|
return session.Count(&Permission{})
|
||||||
@@ -345,20 +342,6 @@ func GetPermissionsByModel(owner string, model string) ([]*Permission, error) {
|
|||||||
return permissions, nil
|
return permissions, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ContainsAsterisk(userId string, users []string) bool {
|
|
||||||
containsAsterisk := false
|
|
||||||
group, _ := util.GetOwnerAndNameFromId(userId)
|
|
||||||
for _, user := range users {
|
|
||||||
permissionGroup, permissionUserName := util.GetOwnerAndNameFromId(user)
|
|
||||||
if permissionGroup == group && permissionUserName == "*" {
|
|
||||||
containsAsterisk = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return containsAsterisk
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetMaskedPermissions(permissions []*Permission) []*Permission {
|
func GetMaskedPermissions(permissions []*Permission) []*Permission {
|
||||||
for _, permission := range permissions {
|
for _, permission := range permissions {
|
||||||
permission.Users = nil
|
permission.Users = nil
|
||||||
@@ -388,3 +371,27 @@ func GroupPermissionsByModelAdapter(permissions []*Permission) map[string][]stri
|
|||||||
|
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Permission) GetId() string {
|
||||||
|
return util.GetId(p.Owner, p.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Permission) isUserHit(name string) bool {
|
||||||
|
targetOrg, _ := util.GetOwnerAndNameFromId(name)
|
||||||
|
for _, user := range p.Users {
|
||||||
|
userOrg, userName := util.GetOwnerAndNameFromId(user)
|
||||||
|
if userOrg == targetOrg && userName == "*" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Permission) isResourceHit(name string) bool {
|
||||||
|
for _, resource := range p.Resources {
|
||||||
|
if name == resource {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@@ -37,7 +37,7 @@ type Product struct {
|
|||||||
Price float64 `json:"price"`
|
Price float64 `json:"price"`
|
||||||
Quantity int `json:"quantity"`
|
Quantity int `json:"quantity"`
|
||||||
Sold int `json:"sold"`
|
Sold int `json:"sold"`
|
||||||
Providers []string `xorm:"varchar(100)" json:"providers"`
|
Providers []string `xorm:"varchar(255)" json:"providers"`
|
||||||
ReturnUrl string `xorm:"varchar(1000)" json:"returnUrl"`
|
ReturnUrl string `xorm:"varchar(1000)" json:"returnUrl"`
|
||||||
|
|
||||||
State string `xorm:"varchar(100)" json:"state"`
|
State string `xorm:"varchar(100)" json:"state"`
|
||||||
@@ -158,24 +158,23 @@ func (product *Product) getProvider(providerName string) (*Provider, error) {
|
|||||||
return provider, nil
|
return provider, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuyProduct(id string, user *User, providerName, pricingName, planName, host string) (string, string, error) {
|
func BuyProduct(id string, user *User, providerName, pricingName, planName, host string) (*Payment, error) {
|
||||||
product, err := GetProduct(id)
|
product, err := GetProduct(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if product == nil {
|
if product == nil {
|
||||||
return "", "", fmt.Errorf("the product: %s does not exist", id)
|
return nil, fmt.Errorf("the product: %s does not exist", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
provider, err := product.getProvider(providerName)
|
provider, err := product.getProvider(providerName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
pProvider, _, err := provider.getPaymentProvider()
|
pProvider, err := GetPaymentProvider(provider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
owner := product.Owner
|
owner := product.Owner
|
||||||
@@ -192,15 +191,15 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
|
|||||||
if pricingName != "" && planName != "" {
|
if pricingName != "" && planName != "" {
|
||||||
plan, err := GetPlan(util.GetId(owner, planName))
|
plan, err := GetPlan(util.GetId(owner, planName))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
if plan == nil {
|
if plan == nil {
|
||||||
return "", "", fmt.Errorf("the plan: %s does not exist", planName)
|
return nil, fmt.Errorf("the plan: %s does not exist", planName)
|
||||||
}
|
}
|
||||||
sub := NewSubscription(owner, user.Name, plan.Name, paymentName, plan.Period)
|
sub := NewSubscription(owner, user.Name, plan.Name, paymentName, plan.Period)
|
||||||
_, err = AddSubscription(sub)
|
_, err = AddSubscription(sub)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
returnUrl = fmt.Sprintf("%s/buy-plan/%s/%s/result?subscription=%s", originFrontend, owner, pricingName, sub.Name)
|
returnUrl = fmt.Sprintf("%s/buy-plan/%s/%s/result?subscription=%s", originFrontend, owner, pricingName, sub.Name)
|
||||||
}
|
}
|
||||||
@@ -208,10 +207,10 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
|
|||||||
// Create an OrderId and get the payUrl
|
// Create an OrderId and get the payUrl
|
||||||
payUrl, orderId, err := pProvider.Pay(providerName, productName, payerName, paymentName, productDisplayName, product.Price, product.Currency, returnUrl, notifyUrl)
|
payUrl, orderId, err := pProvider.Pay(providerName, productName, payerName, paymentName, productDisplayName, product.Price, product.Currency, returnUrl, notifyUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Create a Payment linked with Product and Order
|
// Create a Payment linked with Product and Order
|
||||||
payment := Payment{
|
payment := &Payment{
|
||||||
Owner: product.Owner,
|
Owner: product.Owner,
|
||||||
Name: paymentName,
|
Name: paymentName,
|
||||||
CreatedTime: util.GetCurrentTime(),
|
CreatedTime: util.GetCurrentTime(),
|
||||||
@@ -230,6 +229,7 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
|
|||||||
|
|
||||||
User: user.Name,
|
User: user.Name,
|
||||||
PayUrl: payUrl,
|
PayUrl: payUrl,
|
||||||
|
SuccessUrl: returnUrl,
|
||||||
State: pp.PaymentStateCreated,
|
State: pp.PaymentStateCreated,
|
||||||
OutOrderId: orderId,
|
OutOrderId: orderId,
|
||||||
}
|
}
|
||||||
@@ -238,15 +238,15 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
|
|||||||
payment.State = pp.PaymentStatePaid
|
payment.State = pp.PaymentStatePaid
|
||||||
}
|
}
|
||||||
|
|
||||||
affected, err := AddPayment(&payment)
|
affected, err := AddPayment(payment)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !affected {
|
if !affected {
|
||||||
return "", "", fmt.Errorf("failed to add payment: %s", util.StructToJson(payment))
|
return nil, fmt.Errorf("failed to add payment: %s", util.StructToJson(payment))
|
||||||
}
|
}
|
||||||
return payUrl, orderId, err
|
return payment, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExtendProductWithProviders(product *Product) error {
|
func ExtendProductWithProviders(product *Product) error {
|
||||||
|
@@ -17,31 +17,24 @@
|
|||||||
|
|
||||||
package object
|
package object
|
||||||
|
|
||||||
import (
|
//func TestProduct(t *testing.T) {
|
||||||
"testing"
|
// InitConfig()
|
||||||
|
//
|
||||||
"github.com/casdoor/casdoor/pp"
|
// product, _ := GetProduct("admin/product_123")
|
||||||
"github.com/casdoor/casdoor/util"
|
// provider, _ := getProvider(product.Owner, "provider_pay_alipay")
|
||||||
)
|
// cert, _ := getCert(product.Owner, "cert-pay-alipay")
|
||||||
|
// pProvider, err := pp.GetPaymentProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, cert.Certificate, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey, provider.ClientId2)
|
||||||
func TestProduct(t *testing.T) {
|
// if err != nil {
|
||||||
InitConfig()
|
// panic(err)
|
||||||
|
// }
|
||||||
product, _ := GetProduct("admin/product_123")
|
//
|
||||||
provider, _ := getProvider(product.Owner, "provider_pay_alipay")
|
// paymentName := util.GenerateTimeId()
|
||||||
cert, _ := getCert(product.Owner, "cert-pay-alipay")
|
// returnUrl := ""
|
||||||
pProvider, err := pp.GetPaymentProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, cert.Certificate, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey, provider.ClientId2)
|
// notifyUrl := ""
|
||||||
if err != nil {
|
// payUrl, _, err := pProvider.Pay(provider.Name, product.Name, "alice", paymentName, product.DisplayName, product.Price, product.Currency, returnUrl, notifyUrl)
|
||||||
panic(err)
|
// if err != nil {
|
||||||
}
|
// panic(err)
|
||||||
|
// }
|
||||||
paymentName := util.GenerateTimeId()
|
//
|
||||||
returnUrl := ""
|
// println(payUrl)
|
||||||
notifyUrl := ""
|
//}
|
||||||
payUrl, _, err := pProvider.Pay(provider.Name, product.Name, "alice", paymentName, product.DisplayName, product.Price, product.Currency, returnUrl, notifyUrl)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
println(payUrl)
|
|
||||||
}
|
|
||||||
|
@@ -251,30 +251,69 @@ func DeleteProvider(provider *Provider) (bool, error) {
|
|||||||
return affected != 0, nil
|
return affected != 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) getPaymentProvider() (pp.PaymentProvider, *Cert, error) {
|
func GetPaymentProvider(p *Provider) (pp.PaymentProvider, error) {
|
||||||
cert := &Cert{}
|
cert := &Cert{}
|
||||||
if p.Cert != "" {
|
if p.Cert != "" {
|
||||||
var err error
|
var err error
|
||||||
cert, err = getCert(p.Owner, p.Cert)
|
cert, err = GetCert(util.GetId(p.Owner, p.Cert))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if cert == nil {
|
if cert == nil {
|
||||||
return nil, nil, fmt.Errorf("the cert: %s does not exist", p.Cert)
|
return nil, fmt.Errorf("the cert: %s does not exist", p.Cert)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
typ := p.Type
|
||||||
pProvider, err := pp.GetPaymentProvider(p.Type, p.ClientId, p.ClientSecret, p.Host, cert.Certificate, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey, p.ClientId2)
|
if typ == "Dummy" {
|
||||||
if err != nil {
|
pp, err := pp.NewDummyPaymentProvider()
|
||||||
return nil, cert, err
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return pp, nil
|
||||||
|
} else if typ == "Alipay" {
|
||||||
|
if p.Metadata != "" {
|
||||||
|
// alipay provider store rootCert's name in metadata
|
||||||
|
rootCert, err := GetCert(util.GetId(p.Owner, p.Metadata))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if rootCert == nil {
|
||||||
|
return nil, fmt.Errorf("the cert: %s does not exist", p.Metadata)
|
||||||
|
}
|
||||||
|
pp, err := pp.NewAlipayPaymentProvider(p.ClientId, cert.Certificate, cert.PrivateKey, rootCert.Certificate, rootCert.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return pp, nil
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("the metadata of alipay provider is empty")
|
||||||
|
}
|
||||||
|
} else if typ == "GC" {
|
||||||
|
return pp.NewGcPaymentProvider(p.ClientId, p.ClientSecret, p.Host), nil
|
||||||
|
} else if typ == "WeChat Pay" {
|
||||||
|
pp, err := pp.NewWechatPaymentProvider(p.ClientId, p.ClientSecret, p.ClientId2, cert.Certificate, cert.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return pp, nil
|
||||||
|
} else if typ == "PayPal" {
|
||||||
|
pp, err := pp.NewPaypalPaymentProvider(p.ClientId, p.ClientSecret)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return pp, nil
|
||||||
|
} else if typ == "Stripe" {
|
||||||
|
pp, err := pp.NewStripePaymentProvider(p.ClientId, p.ClientSecret)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return pp, nil
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("the payment provider type: %s is not supported", p.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
if pProvider == nil {
|
return nil, nil
|
||||||
return nil, cert, fmt.Errorf("the payment provider type: %s is not supported", p.Type)
|
|
||||||
}
|
|
||||||
|
|
||||||
return pProvider, cert, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) GetId() string {
|
func (p *Provider) GetId() string {
|
||||||
|
@@ -15,6 +15,7 @@
|
|||||||
package object
|
package object
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -31,7 +32,7 @@ type Credential struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (syncer *Syncer) getOriginalUsers() ([]*OriginalUser, error) {
|
func (syncer *Syncer) getOriginalUsers() ([]*OriginalUser, error) {
|
||||||
var results []map[string]string
|
var results []map[string]sql.NullString
|
||||||
err := syncer.Ormer.Engine.Table(syncer.getTable()).Find(&results)
|
err := syncer.Ormer.Engine.Table(syncer.getTable()).Find(&results)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@@ -15,6 +15,7 @@
|
|||||||
package object
|
package object
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -196,7 +197,7 @@ func (syncer *Syncer) getUserValue(user *User, key string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (syncer *Syncer) getOriginalUsersFromMap(results []map[string]string) []*OriginalUser {
|
func (syncer *Syncer) getOriginalUsersFromMap(results []map[string]sql.NullString) []*OriginalUser {
|
||||||
users := []*OriginalUser{}
|
users := []*OriginalUser{}
|
||||||
for _, result := range results {
|
for _, result := range results {
|
||||||
originalUser := &OriginalUser{
|
originalUser := &OriginalUser{
|
||||||
@@ -216,11 +217,11 @@ func (syncer *Syncer) getOriginalUsersFromMap(results []map[string]string) []*Or
|
|||||||
names := strings.Split(tableColumnName, "+")
|
names := strings.Split(tableColumnName, "+")
|
||||||
var values []string
|
var values []string
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
values = append(values, result[strings.Trim(name, " ")])
|
values = append(values, result[strings.Trim(name, " ")].String)
|
||||||
}
|
}
|
||||||
value = strings.Join(values, " ")
|
value = strings.Join(values, " ")
|
||||||
} else {
|
} else {
|
||||||
value = result[tableColumnName]
|
value = result[tableColumnName].String
|
||||||
}
|
}
|
||||||
syncer.setUserByKeyValue(originalUser, tableColumn.CasdoorName, value)
|
syncer.setUserByKeyValue(originalUser, tableColumn.CasdoorName, value)
|
||||||
}
|
}
|
||||||
@@ -249,9 +250,9 @@ func (syncer *Syncer) getOriginalUsersFromMap(results []map[string]string) []*Or
|
|||||||
// enable
|
// enable
|
||||||
value, ok := result["ENABLED"]
|
value, ok := result["ENABLED"]
|
||||||
if ok {
|
if ok {
|
||||||
originalUser.IsForbidden = !util.ParseBool(value)
|
originalUser.IsForbidden = !util.ParseBool(value.String)
|
||||||
} else {
|
} else {
|
||||||
originalUser.IsForbidden = !util.ParseBool(result["enabled"])
|
originalUser.IsForbidden = !util.ParseBool(result["enabled"].String)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -185,38 +185,57 @@ func StoreCasTokenForProxyTicket(token *CasAuthenticationSuccess, targetService,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GenerateCasToken(userId string, service string) (string, error) {
|
func GenerateCasToken(userId string, service string) (string, error) {
|
||||||
if user, err := GetUser(userId); err != nil {
|
user, err := GetUser(userId)
|
||||||
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
} else if user != nil {
|
|
||||||
authenticationSuccess := CasAuthenticationSuccess{
|
|
||||||
User: user.Name,
|
|
||||||
Attributes: &CasAttributes{
|
|
||||||
AuthenticationDate: time.Now(),
|
|
||||||
UserAttributes: &CasUserAttributes{},
|
|
||||||
},
|
|
||||||
ProxyGrantingTicket: fmt.Sprintf("PGTIOU-%s", util.GenerateId()),
|
|
||||||
}
|
|
||||||
data, _ := json.Marshal(user)
|
|
||||||
tmp := map[string]string{}
|
|
||||||
json.Unmarshal(data, &tmp)
|
|
||||||
for k, v := range tmp {
|
|
||||||
if v != "" {
|
|
||||||
authenticationSuccess.Attributes.UserAttributes.Attributes = append(authenticationSuccess.Attributes.UserAttributes.Attributes, &CasNamedAttribute{
|
|
||||||
Name: k,
|
|
||||||
Value: v,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
st := fmt.Sprintf("ST-%d", rand.Int())
|
|
||||||
stToServiceResponse.Store(st, &CasAuthenticationSuccessWrapper{
|
|
||||||
AuthenticationSuccess: &authenticationSuccess,
|
|
||||||
Service: service,
|
|
||||||
UserId: userId,
|
|
||||||
})
|
|
||||||
return st, nil
|
|
||||||
} else {
|
|
||||||
return "", fmt.Errorf("invalid user Id")
|
|
||||||
}
|
}
|
||||||
|
if user == nil {
|
||||||
|
return "", fmt.Errorf("The user: %s doesn't exist", userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
user, _ = GetMaskedUser(user, false)
|
||||||
|
|
||||||
|
authenticationSuccess := CasAuthenticationSuccess{
|
||||||
|
User: user.Name,
|
||||||
|
Attributes: &CasAttributes{
|
||||||
|
AuthenticationDate: time.Now(),
|
||||||
|
UserAttributes: &CasUserAttributes{},
|
||||||
|
},
|
||||||
|
ProxyGrantingTicket: fmt.Sprintf("PGTIOU-%s", util.GenerateId()),
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(user)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp := map[string]interface{}{}
|
||||||
|
err = json.Unmarshal(data, &tmp)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range tmp {
|
||||||
|
value := fmt.Sprintf("%v", v)
|
||||||
|
if value == "<nil>" || value == "[]" || value == "map[]" {
|
||||||
|
value = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if value != "" {
|
||||||
|
authenticationSuccess.Attributes.UserAttributes.Attributes = append(authenticationSuccess.Attributes.UserAttributes.Attributes, &CasNamedAttribute{
|
||||||
|
Name: k,
|
||||||
|
Value: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
st := fmt.Sprintf("ST-%d", rand.Int())
|
||||||
|
stToServiceResponse.Store(st, &CasAuthenticationSuccessWrapper{
|
||||||
|
AuthenticationSuccess: &authenticationSuccess,
|
||||||
|
Service: service,
|
||||||
|
UserId: userId,
|
||||||
|
})
|
||||||
|
return st, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetValidationBySaml
|
// GetValidationBySaml
|
||||||
|
@@ -194,16 +194,17 @@ type User struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Userinfo struct {
|
type Userinfo struct {
|
||||||
Sub string `json:"sub"`
|
Sub string `json:"sub"`
|
||||||
Iss string `json:"iss"`
|
Iss string `json:"iss"`
|
||||||
Aud string `json:"aud"`
|
Aud string `json:"aud"`
|
||||||
Name string `json:"preferred_username,omitempty"`
|
Name string `json:"preferred_username,omitempty"`
|
||||||
DisplayName string `json:"name,omitempty"`
|
DisplayName string `json:"name,omitempty"`
|
||||||
Email string `json:"email,omitempty"`
|
Email string `json:"email,omitempty"`
|
||||||
Avatar string `json:"picture,omitempty"`
|
EmailVerified bool `json:"email_verified,omitempty"`
|
||||||
Address string `json:"address,omitempty"`
|
Avatar string `json:"picture,omitempty"`
|
||||||
Phone string `json:"phone,omitempty"`
|
Address string `json:"address,omitempty"`
|
||||||
Groups []string `json:"groups,omitempty"`
|
Phone string `json:"phone,omitempty"`
|
||||||
|
Groups []string `json:"groups,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ManagedAccount struct {
|
type ManagedAccount struct {
|
||||||
@@ -757,6 +758,7 @@ func GetUserInfo(user *User, scope string, aud string, host string) *Userinfo {
|
|||||||
}
|
}
|
||||||
if strings.Contains(scope, "email") {
|
if strings.Contains(scope, "email") {
|
||||||
resp.Email = user.Email
|
resp.Email = user.Email
|
||||||
|
resp.EmailVerified = user.EmailVerified
|
||||||
}
|
}
|
||||||
if strings.Contains(scope, "address") {
|
if strings.Contains(scope, "address") {
|
||||||
resp.Address = user.Location
|
resp.Address = user.Location
|
||||||
|
@@ -35,7 +35,7 @@ func downloadImage(client *http.Client, url string) (*bytes.Buffer, string, erro
|
|||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("downloadImage() error for url [%s]: %s\n", url, err.Error())
|
fmt.Printf("downloadImage() error for url [%s]: %s\n", url, err.Error())
|
||||||
if strings.Contains(err.Error(), "EOF") || strings.Contains(err.Error(), "no such host") {
|
if strings.Contains(err.Error(), "EOF") || strings.Contains(err.Error(), "no such host") || strings.Contains(err.Error(), "did not properly respond after a period of time") {
|
||||||
return nil, "", nil
|
return nil, "", nil
|
||||||
} else {
|
} else {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
|
75
pp/alipay.go
75
pp/alipay.go
@@ -16,9 +16,9 @@ package pp
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/util"
|
|
||||||
"github.com/go-pay/gopay"
|
"github.com/go-pay/gopay"
|
||||||
"github.com/go-pay/gopay/alipay"
|
"github.com/go-pay/gopay/alipay"
|
||||||
)
|
)
|
||||||
@@ -28,6 +28,11 @@ type AlipayPaymentProvider struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewAlipayPaymentProvider(appId string, appCertificate string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) (*AlipayPaymentProvider, error) {
|
func NewAlipayPaymentProvider(appId string, appCertificate string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) (*AlipayPaymentProvider, error) {
|
||||||
|
// clientId => appId
|
||||||
|
// cert.Certificate => appCertificate
|
||||||
|
// cert.PrivateKey => appPrivateKey
|
||||||
|
// rootCert.Certificate => authorityPublicKey
|
||||||
|
// rootCert.PrivateKey => authorityRootPublicKey
|
||||||
pp := &AlipayPaymentProvider{}
|
pp := &AlipayPaymentProvider{}
|
||||||
|
|
||||||
client, err := alipay.NewClient(appId, appPrivateKey, true)
|
client, err := alipay.NewClient(appId, appPrivateKey, true)
|
||||||
@@ -46,54 +51,60 @@ func NewAlipayPaymentProvider(appId string, appCertificate string, appPrivateKey
|
|||||||
|
|
||||||
func (pp *AlipayPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) {
|
func (pp *AlipayPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) {
|
||||||
// pp.Client.DebugSwitch = gopay.DebugOn
|
// pp.Client.DebugSwitch = gopay.DebugOn
|
||||||
|
|
||||||
bm := gopay.BodyMap{}
|
bm := gopay.BodyMap{}
|
||||||
|
pp.Client.SetReturnUrl(returnUrl)
|
||||||
bm.Set("providerName", providerName)
|
pp.Client.SetNotifyUrl(notifyUrl)
|
||||||
bm.Set("productName", productName)
|
bm.Set("subject", joinAttachString([]string{productName, productDisplayName, providerName}))
|
||||||
|
|
||||||
bm.Set("return_url", returnUrl)
|
|
||||||
bm.Set("notify_url", notifyUrl)
|
|
||||||
|
|
||||||
bm.Set("subject", productDisplayName)
|
|
||||||
bm.Set("out_trade_no", paymentName)
|
bm.Set("out_trade_no", paymentName)
|
||||||
bm.Set("total_amount", getPriceString(price))
|
bm.Set("total_amount", priceFloat64ToString(price))
|
||||||
|
|
||||||
payUrl, err := pp.Client.TradePagePay(context.Background(), bm)
|
payUrl, err := pp.Client.TradePagePay(context.Background(), bm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
return payUrl, "", nil
|
return payUrl, paymentName, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *AlipayPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) {
|
func (pp *AlipayPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {
|
||||||
bm, err := alipay.ParseNotifyToBodyMap(request)
|
bm := gopay.BodyMap{}
|
||||||
|
bm.Set("out_trade_no", orderId)
|
||||||
|
aliRsp, err := pp.Client.TradeQuery(context.Background(), bm)
|
||||||
|
notifyResult := &NotifyResult{}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
errRsp := &alipay.ErrorResponse{}
|
||||||
|
unmarshalErr := json.Unmarshal([]byte(err.Error()), errRsp)
|
||||||
|
if unmarshalErr != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if errRsp.SubCode == "ACQ.TRADE_NOT_EXIST" {
|
||||||
|
notifyResult.PaymentStatus = PaymentStateCanceled
|
||||||
|
return notifyResult, nil
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
switch aliRsp.Response.TradeStatus {
|
||||||
providerName := bm.Get("providerName")
|
case "WAIT_BUYER_PAY":
|
||||||
productName := bm.Get("productName")
|
notifyResult.PaymentStatus = PaymentStateCreated
|
||||||
|
return notifyResult, nil
|
||||||
productDisplayName := bm.Get("subject")
|
case "TRADE_CLOSED":
|
||||||
paymentName := bm.Get("out_trade_no")
|
notifyResult.PaymentStatus = PaymentStateTimeout
|
||||||
price := util.ParseFloat(bm.Get("total_amount"))
|
return notifyResult, nil
|
||||||
|
case "TRADE_SUCCESS":
|
||||||
ok, err := alipay.VerifySignWithCert(authorityPublicKey, bm)
|
// skip
|
||||||
if err != nil {
|
default:
|
||||||
return nil, err
|
notifyResult.PaymentStatus = PaymentStateError
|
||||||
|
notifyResult.NotifyMessage = fmt.Sprintf("unexpected alipay trade state: %v", aliRsp.Response.TradeStatus)
|
||||||
|
return notifyResult, nil
|
||||||
}
|
}
|
||||||
if !ok {
|
productDisplayName, productName, providerName, _ := parseAttachString(aliRsp.Response.Subject)
|
||||||
return nil, err
|
notifyResult = &NotifyResult{
|
||||||
}
|
|
||||||
notifyResult := &NotifyResult{
|
|
||||||
ProductName: productName,
|
ProductName: productName,
|
||||||
ProductDisplayName: productDisplayName,
|
ProductDisplayName: productDisplayName,
|
||||||
ProviderName: providerName,
|
ProviderName: providerName,
|
||||||
OrderId: orderId,
|
OrderId: orderId,
|
||||||
PaymentStatus: PaymentStatePaid,
|
PaymentStatus: PaymentStatePaid,
|
||||||
Price: price,
|
Price: priceStringToFloat64(aliRsp.Response.TotalAmount),
|
||||||
PaymentName: paymentName,
|
PaymentName: orderId,
|
||||||
}
|
}
|
||||||
return notifyResult, nil
|
return notifyResult, nil
|
||||||
}
|
}
|
||||||
|
@@ -14,8 +14,6 @@
|
|||||||
|
|
||||||
package pp
|
package pp
|
||||||
|
|
||||||
import "net/http"
|
|
||||||
|
|
||||||
type DummyPaymentProvider struct{}
|
type DummyPaymentProvider struct{}
|
||||||
|
|
||||||
func NewDummyPaymentProvider() (*DummyPaymentProvider, error) {
|
func NewDummyPaymentProvider() (*DummyPaymentProvider, error) {
|
||||||
@@ -27,7 +25,7 @@ func (pp *DummyPaymentProvider) Pay(providerName string, productName string, pay
|
|||||||
return returnUrl, "", nil
|
return returnUrl, "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *DummyPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) {
|
func (pp *DummyPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {
|
||||||
return &NotifyResult{
|
return &NotifyResult{
|
||||||
PaymentStatus: PaymentStatePaid,
|
PaymentStatus: PaymentStatePaid,
|
||||||
}, nil
|
}, nil
|
||||||
|
2
pp/gc.go
2
pp/gc.go
@@ -216,7 +216,7 @@ func (pp *GcPaymentProvider) Pay(providerName string, productName string, payerN
|
|||||||
return payRespInfo.PayUrl, "", nil
|
return payRespInfo.PayUrl, "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *GcPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) {
|
func (pp *GcPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {
|
||||||
reqBody := GcRequestBody{}
|
reqBody := GcRequestBody{}
|
||||||
m, err := url.ParseQuery(string(body))
|
m, err := url.ParseQuery(string(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -18,7 +18,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/conf"
|
"github.com/casdoor/casdoor/conf"
|
||||||
@@ -88,7 +87,7 @@ func (pp *PaypalPaymentProvider) Pay(providerName string, productName string, pa
|
|||||||
return ppRsp.Response.Links[1].Href, ppRsp.Response.Id, nil
|
return ppRsp.Response.Links[1].Href, ppRsp.Response.Id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *PaypalPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) {
|
func (pp *PaypalPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {
|
||||||
notifyResult := &NotifyResult{}
|
notifyResult := &NotifyResult{}
|
||||||
captureRsp, err := pp.Client.OrderCapture(context.Background(), orderId, nil)
|
captureRsp, err := pp.Client.OrderCapture(context.Background(), orderId, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -14,8 +14,6 @@
|
|||||||
|
|
||||||
package pp
|
package pp
|
||||||
|
|
||||||
import "net/http"
|
|
||||||
|
|
||||||
type PaymentState string
|
type PaymentState string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -42,45 +40,7 @@ type NotifyResult struct {
|
|||||||
|
|
||||||
type PaymentProvider interface {
|
type PaymentProvider interface {
|
||||||
Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error)
|
Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error)
|
||||||
Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error)
|
Notify(body []byte, orderId string) (*NotifyResult, error)
|
||||||
GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error)
|
GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error)
|
||||||
GetResponseError(err error) string
|
GetResponseError(err error) string
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPaymentProvider(typ string, clientId string, clientSecret string, host string, appCertificate string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string, clientId2 string) (PaymentProvider, error) {
|
|
||||||
if typ == "Dummy" {
|
|
||||||
pp, err := NewDummyPaymentProvider()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return pp, nil
|
|
||||||
} else if typ == "Alipay" {
|
|
||||||
pp, err := NewAlipayPaymentProvider(clientId, appCertificate, appPrivateKey, authorityPublicKey, authorityRootPublicKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return pp, nil
|
|
||||||
} else if typ == "GC" {
|
|
||||||
return NewGcPaymentProvider(clientId, clientSecret, host), nil
|
|
||||||
} else if typ == "WeChat Pay" {
|
|
||||||
pp, err := NewWechatPaymentProvider(clientId, clientSecret, clientId2, appCertificate, appPrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return pp, nil
|
|
||||||
} else if typ == "PayPal" {
|
|
||||||
pp, err := NewPaypalPaymentProvider(clientId, clientSecret)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return pp, nil
|
|
||||||
} else if typ == "Stripe" {
|
|
||||||
pp, err := NewStripePaymentProvider(clientId, clientSecret)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return pp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
@@ -16,7 +16,6 @@ package pp
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/conf"
|
"github.com/casdoor/casdoor/conf"
|
||||||
@@ -94,7 +93,7 @@ func (pp *StripePaymentProvider) Pay(providerName string, productName string, pa
|
|||||||
return sCheckout.URL, sCheckout.ID, nil
|
return sCheckout.URL, sCheckout.ID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *StripePaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) {
|
func (pp *StripePaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {
|
||||||
notifyResult := &NotifyResult{}
|
notifyResult := &NotifyResult{}
|
||||||
sCheckout, err := stripeCheckout.Get(orderId, nil)
|
sCheckout, err := stripeCheckout.Get(orderId, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -49,3 +49,11 @@ func priceFloat64ToInt64(price float64) int64 {
|
|||||||
func priceFloat64ToString(price float64) string {
|
func priceFloat64ToString(price float64) string {
|
||||||
return strconv.FormatFloat(price, 'f', 2, 64)
|
return strconv.FormatFloat(price, 'f', 2, 64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func priceStringToFloat64(price string) float64 {
|
||||||
|
f, err := strconv.ParseFloat(price, 64)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
@@ -17,7 +17,7 @@ package pp
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"fmt"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"github.com/go-pay/gopay"
|
"github.com/go-pay/gopay"
|
||||||
@@ -31,17 +31,22 @@ type WechatPayNotifyResponse struct {
|
|||||||
|
|
||||||
type WechatPaymentProvider struct {
|
type WechatPaymentProvider struct {
|
||||||
Client *wechat.ClientV3
|
Client *wechat.ClientV3
|
||||||
appId string
|
AppId string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWechatPaymentProvider(mchId string, apiV3Key string, appId string, mchCertSerialNumber string, privateKey string) (*WechatPaymentProvider, error) {
|
func NewWechatPaymentProvider(mchId string, apiV3Key string, appId string, serialNo string, privateKey string) (*WechatPaymentProvider, error) {
|
||||||
if appId == "" && mchId == "" && mchCertSerialNumber == "" && apiV3Key == "" && privateKey == "" {
|
// https://pay.weixin.qq.com/docs/merchant/products/native-payment/preparation.html
|
||||||
|
// clientId => mchId
|
||||||
|
// clientSecret => apiV3Key
|
||||||
|
// clientId2 => appId
|
||||||
|
|
||||||
|
// appCertificate => serialNo
|
||||||
|
// appPrivateKey => privateKey
|
||||||
|
if appId == "" || mchId == "" || serialNo == "" || apiV3Key == "" || privateKey == "" {
|
||||||
return &WechatPaymentProvider{}, nil
|
return &WechatPaymentProvider{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
pp := &WechatPaymentProvider{appId: appId}
|
clientV3, err := wechat.NewClientV3(mchId, serialNo, apiV3Key, privateKey)
|
||||||
|
|
||||||
clientV3, err := wechat.NewClientV3(mchId, mchCertSerialNumber, apiV3Key, privateKey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -50,73 +55,70 @@ func NewWechatPaymentProvider(mchId string, apiV3Key string, appId string, mchCe
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
pp := &WechatPaymentProvider{
|
||||||
pp.Client = clientV3.SetPlatformCert([]byte(platformCert), serialNo)
|
Client: clientV3.SetPlatformCert([]byte(platformCert), serialNo),
|
||||||
|
AppId: appId,
|
||||||
|
}
|
||||||
|
|
||||||
return pp, nil
|
return pp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *WechatPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) {
|
func (pp *WechatPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) {
|
||||||
// pp.Client.DebugSwitch = gopay.DebugOn
|
|
||||||
|
|
||||||
bm := gopay.BodyMap{}
|
bm := gopay.BodyMap{}
|
||||||
|
|
||||||
bm.Set("attach", joinAttachString([]string{productDisplayName, productName, providerName}))
|
bm.Set("attach", joinAttachString([]string{productDisplayName, productName, providerName}))
|
||||||
bm.Set("appid", pp.appId)
|
bm.Set("appid", pp.AppId)
|
||||||
bm.Set("description", productDisplayName)
|
bm.Set("description", productDisplayName)
|
||||||
bm.Set("notify_url", notifyUrl)
|
bm.Set("notify_url", notifyUrl)
|
||||||
bm.Set("out_trade_no", paymentName)
|
bm.Set("out_trade_no", paymentName)
|
||||||
bm.SetBodyMap("amount", func(bm gopay.BodyMap) {
|
bm.SetBodyMap("amount", func(bm gopay.BodyMap) {
|
||||||
bm.Set("total", int(price*100))
|
bm.Set("total", priceFloat64ToInt64(price))
|
||||||
bm.Set("currency", "CNY")
|
bm.Set("currency", currency)
|
||||||
})
|
})
|
||||||
|
|
||||||
wxRsp, err := pp.Client.V3TransactionNative(context.Background(), bm)
|
nativeRsp, err := pp.Client.V3TransactionNative(context.Background(), bm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
if nativeRsp.Code != wechat.Success {
|
||||||
if wxRsp.Code != wechat.Success {
|
return "", "", errors.New(nativeRsp.Error)
|
||||||
return "", "", errors.New(wxRsp.Error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return wxRsp.Response.CodeUrl, "", nil
|
return nativeRsp.Response.CodeUrl, paymentName, nil // Wechat can use paymentName as the OutTradeNo to query order status
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *WechatPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) {
|
func (pp *WechatPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {
|
||||||
notifyReq, err := wechat.V3ParseNotify(request)
|
notifyResult := &NotifyResult{}
|
||||||
|
queryRsp, err := pp.Client.V3TransactionQueryOrder(context.Background(), wechat.OutTradeNo, orderId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if queryRsp.Code != wechat.Success {
|
||||||
cert := pp.Client.WxPublicKey()
|
return nil, errors.New(queryRsp.Error)
|
||||||
err = notifyReq.VerifySignByPK(cert)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
apiKey := string(pp.Client.ApiV3Key)
|
switch queryRsp.Response.TradeState {
|
||||||
result, err := notifyReq.DecryptCipherText(apiKey)
|
case "SUCCESS":
|
||||||
if err != nil {
|
// skip
|
||||||
return nil, err
|
case "CLOSED":
|
||||||
|
notifyResult.PaymentStatus = PaymentStateCanceled
|
||||||
|
return notifyResult, nil
|
||||||
|
case "NOTPAY", "USERPAYING": // not-pad: waiting for user to pay; user-paying: user is paying
|
||||||
|
notifyResult.PaymentStatus = PaymentStateCreated
|
||||||
|
return notifyResult, nil
|
||||||
|
default:
|
||||||
|
notifyResult.PaymentStatus = PaymentStateError
|
||||||
|
notifyResult.NotifyMessage = fmt.Sprintf("unexpected wechat trade state: %v", queryRsp.Response.TradeState)
|
||||||
|
return notifyResult, nil
|
||||||
}
|
}
|
||||||
|
productDisplayName, productName, providerName, _ := parseAttachString(queryRsp.Response.Attach)
|
||||||
paymentName := result.OutTradeNo
|
notifyResult = &NotifyResult{
|
||||||
price := float64(result.Amount.PayerTotal) / 100
|
|
||||||
|
|
||||||
productDisplayName, productName, providerName, err := parseAttachString(result.Attach)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyResult := &NotifyResult{
|
|
||||||
ProductName: productName,
|
ProductName: productName,
|
||||||
ProductDisplayName: productDisplayName,
|
ProductDisplayName: productDisplayName,
|
||||||
ProviderName: providerName,
|
ProviderName: providerName,
|
||||||
OrderId: orderId,
|
OrderId: orderId,
|
||||||
Price: price,
|
Price: priceInt64ToFloat64(int64(queryRsp.Response.Amount.Total)),
|
||||||
PaymentStatus: PaymentStatePaid,
|
PaymentStatus: PaymentStatePaid,
|
||||||
PaymentName: paymentName,
|
PaymentName: queryRsp.Response.OutTradeNo,
|
||||||
}
|
}
|
||||||
return notifyResult, nil
|
return notifyResult, nil
|
||||||
}
|
}
|
||||||
|
@@ -29,7 +29,13 @@ func AutoSigninFilter(ctx *context.Context) {
|
|||||||
|
|
||||||
// GET parameter like "/page?access_token=123" or
|
// GET parameter like "/page?access_token=123" or
|
||||||
// HTTP Bearer token like "Authorization: Bearer 123"
|
// HTTP Bearer token like "Authorization: Bearer 123"
|
||||||
accessToken := util.GetMaxLenStr(ctx.Input.Query("accessToken"), ctx.Input.Query("access_token"), parseBearerToken(ctx))
|
accessToken := ctx.Input.Query("accessToken")
|
||||||
|
if accessToken == "" {
|
||||||
|
accessToken = ctx.Input.Query("access_token")
|
||||||
|
}
|
||||||
|
if accessToken == "" {
|
||||||
|
accessToken = parseBearerToken(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
if accessToken != "" {
|
if accessToken != "" {
|
||||||
token, err := object.GetTokenByAccessToken(accessToken)
|
token, err := object.GetTokenByAccessToken(accessToken)
|
||||||
|
@@ -40,6 +40,13 @@ func CorsFilter(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ctx.Request.RequestURI == "/api/userinfo" {
|
||||||
|
ctx.Output.Header(headerAllowOrigin, origin)
|
||||||
|
ctx.Output.Header(headerAllowMethods, "POST, GET, OPTIONS, DELETE")
|
||||||
|
ctx.Output.Header(headerAllowHeaders, "Content-Type, Authorization")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if origin != "" && originConf != "" && origin != originConf {
|
if origin != "" && originConf != "" && origin != originConf {
|
||||||
ok, err := object.IsOriginAllowed(origin)
|
ok, err := object.IsOriginAllowed(origin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -247,10 +247,10 @@ func initAPI() {
|
|||||||
beego.Router("/api/send-sms", &controllers.ApiController{}, "POST:SendSms")
|
beego.Router("/api/send-sms", &controllers.ApiController{}, "POST:SendSms")
|
||||||
beego.Router("/api/send-notification", &controllers.ApiController{}, "POST:SendNotification")
|
beego.Router("/api/send-notification", &controllers.ApiController{}, "POST:SendNotification")
|
||||||
|
|
||||||
beego.Router("/api/webauthn/signup/begin", &controllers.ApiController{}, "Get:WebAuthnSignupBegin")
|
beego.Router("/api/webauthn/signup/begin", &controllers.ApiController{}, "GET:WebAuthnSignupBegin")
|
||||||
beego.Router("/api/webauthn/signup/finish", &controllers.ApiController{}, "Post:WebAuthnSignupFinish")
|
beego.Router("/api/webauthn/signup/finish", &controllers.ApiController{}, "POST:WebAuthnSignupFinish")
|
||||||
beego.Router("/api/webauthn/signin/begin", &controllers.ApiController{}, "Get:WebAuthnSigninBegin")
|
beego.Router("/api/webauthn/signin/begin", &controllers.ApiController{}, "GET:WebAuthnSigninBegin")
|
||||||
beego.Router("/api/webauthn/signin/finish", &controllers.ApiController{}, "Post:WebAuthnSigninFinish")
|
beego.Router("/api/webauthn/signin/finish", &controllers.ApiController{}, "POST:WebAuthnSigninFinish")
|
||||||
|
|
||||||
beego.Router("/api/mfa/setup/initiate", &controllers.ApiController{}, "POST:MfaSetupInitiate")
|
beego.Router("/api/mfa/setup/initiate", &controllers.ApiController{}, "POST:MfaSetupInitiate")
|
||||||
beego.Router("/api/mfa/setup/verify", &controllers.ApiController{}, "POST:MfaSetupVerify")
|
beego.Router("/api/mfa/setup/verify", &controllers.ApiController{}, "POST:MfaSetupVerify")
|
||||||
|
@@ -15,6 +15,7 @@
|
|||||||
package storage
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -23,76 +24,69 @@ import (
|
|||||||
"github.com/casdoor/oss"
|
"github.com/casdoor/oss"
|
||||||
)
|
)
|
||||||
|
|
||||||
var baseFolder = "files"
|
// LocalFileSystemProvider file system storage
|
||||||
|
type LocalFileSystemProvider struct {
|
||||||
// FileSystem file system storage
|
BaseDir string
|
||||||
type FileSystem struct {
|
|
||||||
Base string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFileSystem initialize the local file system storage
|
// NewLocalFileSystemStorageProvider initialize the local file system storage
|
||||||
func NewFileSystem(base string) *FileSystem {
|
func NewLocalFileSystemStorageProvider() *LocalFileSystemProvider {
|
||||||
absBase, err := filepath.Abs(base)
|
baseFolder := "files"
|
||||||
|
absBase, err := filepath.Abs(baseFolder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("local file system storage's base folder is not initialized")
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &FileSystem{Base: absBase}
|
return &LocalFileSystemProvider{BaseDir: absBase}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFullPath get full path from absolute/relative path
|
// GetFullPath get full path from absolute/relative path
|
||||||
func (fileSystem FileSystem) GetFullPath(path string) string {
|
func (sp LocalFileSystemProvider) GetFullPath(path string) string {
|
||||||
fullPath := path
|
fullPath := path
|
||||||
if !strings.HasPrefix(path, fileSystem.Base) {
|
if !strings.HasPrefix(path, sp.BaseDir) {
|
||||||
fullPath, _ = filepath.Abs(filepath.Join(fileSystem.Base, path))
|
fullPath, _ = filepath.Abs(filepath.Join(sp.BaseDir, path))
|
||||||
}
|
}
|
||||||
return fullPath
|
return fullPath
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get receive file with given path
|
// Get receive file with given path
|
||||||
func (fileSystem FileSystem) Get(path string) (*os.File, error) {
|
func (sp LocalFileSystemProvider) Get(path string) (*os.File, error) {
|
||||||
return os.Open(fileSystem.GetFullPath(path))
|
return os.Open(sp.GetFullPath(path))
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStream get file as stream
|
// GetStream get file as stream
|
||||||
func (fileSystem FileSystem) GetStream(path string) (io.ReadCloser, error) {
|
func (sp LocalFileSystemProvider) GetStream(path string) (io.ReadCloser, error) {
|
||||||
return os.Open(fileSystem.GetFullPath(path))
|
return os.Open(sp.GetFullPath(path))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put store a reader into given path
|
// Put store a reader into given path
|
||||||
func (fileSystem FileSystem) Put(path string, reader io.Reader) (*oss.Object, error) {
|
func (sp LocalFileSystemProvider) Put(path string, reader io.Reader) (*oss.Object, error) {
|
||||||
var (
|
fullPath := sp.GetFullPath(path)
|
||||||
fullPath = fileSystem.GetFullPath(path)
|
|
||||||
err = os.MkdirAll(filepath.Dir(fullPath), os.ModePerm)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
err := os.MkdirAll(filepath.Dir(fullPath), os.ModePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("Casdoor fails to create folder: \"%s\" for local file system storage provider: %s. Make sure Casdoor process has correct permission to create/access it, or you can create it manually in advance", filepath.Dir(fullPath), err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
dst, err := os.Create(filepath.Clean(fullPath))
|
dst, err := os.Create(filepath.Clean(fullPath))
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if seeker, ok := reader.(io.ReadSeeker); ok {
|
if seeker, ok := reader.(io.ReadSeeker); ok {
|
||||||
seeker.Seek(0, 0)
|
seeker.Seek(0, 0)
|
||||||
}
|
}
|
||||||
_, err = io.Copy(dst, reader)
|
_, err = io.Copy(dst, reader)
|
||||||
}
|
}
|
||||||
|
return &oss.Object{Path: path, Name: filepath.Base(path), StorageInterface: sp}, err
|
||||||
return &oss.Object{Path: path, Name: filepath.Base(path), StorageInterface: fileSystem}, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete delete file
|
// Delete delete file
|
||||||
func (fileSystem FileSystem) Delete(path string) error {
|
func (sp LocalFileSystemProvider) Delete(path string) error {
|
||||||
return os.Remove(fileSystem.GetFullPath(path))
|
return os.Remove(sp.GetFullPath(path))
|
||||||
}
|
}
|
||||||
|
|
||||||
// List list all objects under current path
|
// List list all objects under current path
|
||||||
func (fileSystem FileSystem) List(path string) ([]*oss.Object, error) {
|
func (sp LocalFileSystemProvider) List(path string) ([]*oss.Object, error) {
|
||||||
var (
|
objects := []*oss.Object{}
|
||||||
objects []*oss.Object
|
fullPath := sp.GetFullPath(path)
|
||||||
fullPath = fileSystem.GetFullPath(path)
|
|
||||||
)
|
|
||||||
|
|
||||||
filepath.Walk(fullPath, func(path string, info os.FileInfo, err error) error {
|
filepath.Walk(fullPath, func(path string, info os.FileInfo, err error) error {
|
||||||
if path == fullPath {
|
if path == fullPath {
|
||||||
@@ -102,10 +96,10 @@ func (fileSystem FileSystem) List(path string) ([]*oss.Object, error) {
|
|||||||
if err == nil && !info.IsDir() {
|
if err == nil && !info.IsDir() {
|
||||||
modTime := info.ModTime()
|
modTime := info.ModTime()
|
||||||
objects = append(objects, &oss.Object{
|
objects = append(objects, &oss.Object{
|
||||||
Path: strings.TrimPrefix(path, fileSystem.Base),
|
Path: strings.TrimPrefix(path, sp.BaseDir),
|
||||||
Name: info.Name(),
|
Name: info.Name(),
|
||||||
LastModified: &modTime,
|
LastModified: &modTime,
|
||||||
StorageInterface: fileSystem,
|
StorageInterface: sp,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -114,16 +108,12 @@ func (fileSystem FileSystem) List(path string) ([]*oss.Object, error) {
|
|||||||
return objects, nil
|
return objects, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetEndpoint get endpoint, FileSystem's endpoint is /
|
// GetEndpoint get endpoint, LocalFileSystemProvider's endpoint is /
|
||||||
func (fileSystem FileSystem) GetEndpoint() string {
|
func (sp LocalFileSystemProvider) GetEndpoint() string {
|
||||||
return "/"
|
return "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetURL get public accessible URL
|
// GetURL get public accessible URL
|
||||||
func (fileSystem FileSystem) GetURL(path string) (url string, err error) {
|
func (sp LocalFileSystemProvider) GetURL(path string) (url string, err error) {
|
||||||
return path, nil
|
return path, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLocalFileSystemStorageProvider(clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
|
|
||||||
return NewFileSystem(baseFolder)
|
|
||||||
}
|
|
||||||
|
@@ -19,7 +19,7 @@ import "github.com/casdoor/oss"
|
|||||||
func GetStorageProvider(providerType string, clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
|
func GetStorageProvider(providerType string, clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
|
||||||
switch providerType {
|
switch providerType {
|
||||||
case "Local File System":
|
case "Local File System":
|
||||||
return NewLocalFileSystemStorageProvider(clientId, clientSecret, region, bucket, endpoint)
|
return NewLocalFileSystemStorageProvider()
|
||||||
case "AWS S3":
|
case "AWS S3":
|
||||||
return NewAwsS3StorageProvider(clientId, clientSecret, region, bucket, endpoint)
|
return NewAwsS3StorageProvider(clientId, clientSecret, region, bucket, endpoint)
|
||||||
case "MinIO":
|
case "MinIO":
|
||||||
|
@@ -187,32 +187,6 @@ func IsStringsEmpty(strs ...string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetMaxLenStr(strs ...string) string {
|
|
||||||
m := 0
|
|
||||||
i := 0
|
|
||||||
for j, str := range strs {
|
|
||||||
l := len(str)
|
|
||||||
if l > m {
|
|
||||||
m = l
|
|
||||||
i = j
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return strs[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetMinLenStr(strs ...string) string {
|
|
||||||
m := int(^uint(0) >> 1)
|
|
||||||
i := 0
|
|
||||||
for j, str := range strs {
|
|
||||||
l := len(str)
|
|
||||||
if l < m {
|
|
||||||
m = l
|
|
||||||
i = j
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return strs[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
func ReadStringFromPath(path string) string {
|
func ReadStringFromPath(path string) string {
|
||||||
data, err := os.ReadFile(filepath.Clean(path))
|
data, err := os.ReadFile(filepath.Clean(path))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -189,45 +189,6 @@ func TestIsStrsEmpty(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetMaxLenStr(t *testing.T) {
|
|
||||||
scenarios := []struct {
|
|
||||||
description string
|
|
||||||
input []string
|
|
||||||
expected interface{}
|
|
||||||
}{
|
|
||||||
{"Should be return casdoor", []string{"", "casdoor", "casbin"}, "casdoor"},
|
|
||||||
{"Should be return casdoor_jdk", []string{"", "casdoor", "casbin", "casdoor_jdk"}, "casdoor_jdk"},
|
|
||||||
{"Should be return empty string", []string{""}, ""},
|
|
||||||
}
|
|
||||||
for _, scenery := range scenarios {
|
|
||||||
t.Run(scenery.description, func(t *testing.T) {
|
|
||||||
actual := GetMaxLenStr(scenery.input...)
|
|
||||||
assert.Equal(t, scenery.expected, actual, "The returned value not is expected")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetMinLenStr(t *testing.T) {
|
|
||||||
scenarios := []struct {
|
|
||||||
description string
|
|
||||||
input []string
|
|
||||||
expected interface{}
|
|
||||||
}{
|
|
||||||
{"Should be return casbin", []string{"casdoor", "casbin"}, "casbin"},
|
|
||||||
{"Should be return casbin", []string{"casdoor", "casbin", "casdoor_jdk"}, "casbin"},
|
|
||||||
{"Should be return empty string", []string{"a", "", "casbin"}, ""},
|
|
||||||
{"Should be return a", []string{"a", "casdoor", "casbin"}, "a"},
|
|
||||||
{"Should be return a", []string{"casdoor", "a", "casbin"}, "a"},
|
|
||||||
{"Should be return a", []string{"casbin", "casdoor", "a"}, "a"},
|
|
||||||
}
|
|
||||||
for _, scenery := range scenarios {
|
|
||||||
t.Run(scenery.description, func(t *testing.T) {
|
|
||||||
actual := GetMinLenStr(scenery.input...)
|
|
||||||
assert.Equal(t, scenery.expected, actual, "The returned value not is expected")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSnakeString(t *testing.T) {
|
func TestSnakeString(t *testing.T) {
|
||||||
scenarios := []struct {
|
scenarios := []struct {
|
||||||
description string
|
description string
|
||||||
|
@@ -34,6 +34,7 @@
|
|||||||
"i18next": "^19.8.9",
|
"i18next": "^19.8.9",
|
||||||
"libphonenumber-js": "^1.10.19",
|
"libphonenumber-js": "^1.10.19",
|
||||||
"moment": "^2.29.1",
|
"moment": "^2.29.1",
|
||||||
|
"qrcode.react": "^3.1.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-app-polyfill": "^3.0.0",
|
"react-app-polyfill": "^3.0.0",
|
||||||
"react-codemirror2": "^7.2.1",
|
"react-codemirror2": "^7.2.1",
|
||||||
@@ -82,6 +83,9 @@
|
|||||||
"@babel/eslint-parser": "^7.18.9",
|
"@babel/eslint-parser": "^7.18.9",
|
||||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||||
"@babel/preset-react": "^7.18.6",
|
"@babel/preset-react": "^7.18.6",
|
||||||
|
"@testing-library/jest-dom": "^4.2.4",
|
||||||
|
"@testing-library/react": "^9.3.2",
|
||||||
|
"@testing-library/user-event": "^7.1.2",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"cypress": "^12.5.1",
|
"cypress": "^12.5.1",
|
||||||
"eslint": "8.22.0",
|
"eslint": "8.22.0",
|
||||||
@@ -91,10 +95,7 @@
|
|||||||
"lint-staged": "^13.0.3",
|
"lint-staged": "^13.0.3",
|
||||||
"stylelint": "^14.11.0",
|
"stylelint": "^14.11.0",
|
||||||
"stylelint-config-recommended-less": "^1.0.4",
|
"stylelint-config-recommended-less": "^1.0.4",
|
||||||
"stylelint-config-standard": "^28.0.0",
|
"stylelint-config-standard": "^28.0.0"
|
||||||
"@testing-library/jest-dom": "^4.2.4",
|
|
||||||
"@testing-library/react": "^9.3.2",
|
|
||||||
"@testing-library/user-event": "^7.1.2"
|
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"src/**/*.{css,less}": [
|
"src/**/*.{css,less}": [
|
||||||
|
@@ -664,7 +664,8 @@ class App extends Component {
|
|||||||
window.location.pathname.startsWith("/cas") ||
|
window.location.pathname.startsWith("/cas") ||
|
||||||
window.location.pathname.startsWith("/auto-signup") ||
|
window.location.pathname.startsWith("/auto-signup") ||
|
||||||
window.location.pathname.startsWith("/select-plan") ||
|
window.location.pathname.startsWith("/select-plan") ||
|
||||||
window.location.pathname.startsWith("/buy-plan");
|
window.location.pathname.startsWith("/buy-plan") ||
|
||||||
|
window.location.pathname.startsWith("/qrcode") ;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPage() {
|
renderPage() {
|
||||||
|
@@ -542,7 +542,7 @@ class ApplicationEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("signup:Terms of Use"), i18next.t("signup:Terms of Use - Tooltip"))} :
|
{Setting.getLabel(i18next.t("signup:Terms of Use"), i18next.t("signup:Terms of Use - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Input value={this.state.application.termsOfUse} style={{marginBottom: "10px"}} onChange={e => {
|
<Input prefix={<LinkOutlined />} value={this.state.application.termsOfUse} style={{marginBottom: "10px"}} onChange={e => {
|
||||||
this.updateApplicationField("termsOfUse", e.target.value);
|
this.updateApplicationField("termsOfUse", e.target.value);
|
||||||
}} />
|
}} />
|
||||||
<Upload maxCount={1} accept=".html" showUploadList={false}
|
<Upload maxCount={1} accept=".html" showUploadList={false}
|
||||||
|
@@ -204,7 +204,7 @@ class BaseListPage extends React.Component {
|
|||||||
this.renderTable(this.state.data)
|
this.renderTable(this.state.data)
|
||||||
}
|
}
|
||||||
<Tour
|
<Tour
|
||||||
open={this.state.isTourVisible}
|
open={Setting.isMobile() ? false : this.state.isTourVisible}
|
||||||
onClose={this.setIsTourVisible}
|
onClose={this.setIsTourVisible}
|
||||||
steps={this.getSteps()}
|
steps={this.getSteps()}
|
||||||
indicatorsRender={(current, total) => (
|
indicatorsRender={(current, total) => (
|
||||||
|
@@ -31,6 +31,7 @@ import CasLogout from "./auth/CasLogout";
|
|||||||
import {authConfig} from "./auth/Auth";
|
import {authConfig} from "./auth/Auth";
|
||||||
import ProductBuyPage from "./ProductBuyPage";
|
import ProductBuyPage from "./ProductBuyPage";
|
||||||
import PaymentResultPage from "./PaymentResultPage";
|
import PaymentResultPage from "./PaymentResultPage";
|
||||||
|
import QrCodePage from "./QrCodePage";
|
||||||
|
|
||||||
class EntryPage extends React.Component {
|
class EntryPage extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -113,6 +114,7 @@ class EntryPage extends React.Component {
|
|||||||
<Route exact path="/select-plan/:owner/:pricingName" render={(props) => <PricingPage {...this.props} pricing={this.state.pricing} onUpdatePricing={onUpdatePricing} {...props} />} />
|
<Route exact path="/select-plan/:owner/:pricingName" render={(props) => <PricingPage {...this.props} pricing={this.state.pricing} onUpdatePricing={onUpdatePricing} {...props} />} />
|
||||||
<Route exact path="/buy-plan/:owner/:pricingName" render={(props) => <ProductBuyPage {...this.props} pricing={this.state.pricing} onUpdatePricing={onUpdatePricing} {...props} />} />
|
<Route exact path="/buy-plan/:owner/:pricingName" render={(props) => <ProductBuyPage {...this.props} pricing={this.state.pricing} onUpdatePricing={onUpdatePricing} {...props} />} />
|
||||||
<Route exact path="/buy-plan/:owner/:pricingName/result" render={(props) => <PaymentResultPage {...this.props} pricing={this.state.pricing} onUpdatePricing={onUpdatePricing} {...props} />} />
|
<Route exact path="/buy-plan/:owner/:pricingName/result" render={(props) => <PaymentResultPage {...this.props} pricing={this.state.pricing} onUpdatePricing={onUpdatePricing} {...props} />} />
|
||||||
|
<Route exact path="/qrcode/:owner/:paymentName" render={(props) => <QrCodePage {...this.props} onUpdateApplication={onUpdateApplication} {...props} />} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@@ -101,7 +101,7 @@ class PaymentResultPage extends React.Component {
|
|||||||
payment: payment,
|
payment: payment,
|
||||||
});
|
});
|
||||||
if (payment.state === "Created") {
|
if (payment.state === "Created") {
|
||||||
if (["PayPal", "Stripe"].includes(payment.type)) {
|
if (["PayPal", "Stripe", "Alipay"].includes(payment.type)) {
|
||||||
this.setState({
|
this.setState({
|
||||||
timeout: setTimeout(async() => {
|
timeout: setTimeout(async() => {
|
||||||
await PaymentBackend.notifyPayment(this.state.owner, this.state.paymentName);
|
await PaymentBackend.notifyPayment(this.state.owner, this.state.paymentName);
|
||||||
|
@@ -17,6 +17,7 @@ import {Button, Card, Col, Input, Row, Select, Switch} from "antd";
|
|||||||
import * as PermissionBackend from "./backend/PermissionBackend";
|
import * as PermissionBackend from "./backend/PermissionBackend";
|
||||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||||
import * as UserBackend from "./backend/UserBackend";
|
import * as UserBackend from "./backend/UserBackend";
|
||||||
|
import * as GroupBackend from "./backend/GroupBackend";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import * as RoleBackend from "./backend/RoleBackend";
|
import * as RoleBackend from "./backend/RoleBackend";
|
||||||
@@ -35,6 +36,7 @@ class PermissionEditPage extends React.Component {
|
|||||||
organizations: [],
|
organizations: [],
|
||||||
model: null,
|
model: null,
|
||||||
users: [],
|
users: [],
|
||||||
|
groups: [],
|
||||||
roles: [],
|
roles: [],
|
||||||
models: [],
|
models: [],
|
||||||
resources: [],
|
resources: [],
|
||||||
@@ -67,6 +69,7 @@ class PermissionEditPage extends React.Component {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.getUsers(permission.owner);
|
this.getUsers(permission.owner);
|
||||||
|
this.getGroups(permission.owner);
|
||||||
this.getRoles(permission.owner);
|
this.getRoles(permission.owner);
|
||||||
this.getModels(permission.owner);
|
this.getModels(permission.owner);
|
||||||
this.getResources(permission.owner);
|
this.getResources(permission.owner);
|
||||||
@@ -97,6 +100,20 @@ class PermissionEditPage extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getGroups(organizationName) {
|
||||||
|
GroupBackend.getGroups(organizationName)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "error") {
|
||||||
|
Setting.showMessage("error", res.msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
groups: res.data,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
getRoles(organizationName) {
|
getRoles(organizationName) {
|
||||||
RoleBackend.getRoles(organizationName)
|
RoleBackend.getRoles(organizationName)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
@@ -192,6 +209,7 @@ class PermissionEditPage extends React.Component {
|
|||||||
<Select virtual={false} style={{width: "100%"}} disabled={!Setting.isAdminUser(this.props.account)} value={this.state.permission.owner} onChange={(owner => {
|
<Select virtual={false} style={{width: "100%"}} disabled={!Setting.isAdminUser(this.props.account)} value={this.state.permission.owner} onChange={(owner => {
|
||||||
this.updatePermissionField("owner", owner);
|
this.updatePermissionField("owner", owner);
|
||||||
this.getUsers(owner);
|
this.getUsers(owner);
|
||||||
|
this.getGroups(owner);
|
||||||
this.getRoles(owner);
|
this.getRoles(owner);
|
||||||
this.getModels(owner);
|
this.getModels(owner);
|
||||||
this.getResources(owner);
|
this.getResources(owner);
|
||||||
@@ -263,6 +281,17 @@ class PermissionEditPage extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("role:Sub groups"), i18next.t("role:Sub groups - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} mode="multiple" style={{width: "100%"}} value={this.state.permission.groups}
|
||||||
|
onChange={(value => {this.updatePermissionField("groups", value);})}
|
||||||
|
options={this.state.groups.map((group) => Setting.getOption(`${group.owner}/${group.name}`, `${group.owner}/${group.name}`))}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
<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("role:Sub roles"), i18next.t("role:Sub roles - Tooltip"))} :
|
{Setting.getLabel(i18next.t("role:Sub roles"), i18next.t("role:Sub roles - Tooltip"))} :
|
||||||
|
@@ -33,6 +33,7 @@ class PermissionListPage extends BaseListPage {
|
|||||||
createdTime: moment().format(),
|
createdTime: moment().format(),
|
||||||
displayName: `New Permission - ${randomName}`,
|
displayName: `New Permission - ${randomName}`,
|
||||||
users: [`${this.props.account.owner}/${this.props.account.name}`],
|
users: [`${this.props.account.owner}/${this.props.account.name}`],
|
||||||
|
groups: [],
|
||||||
roles: [],
|
roles: [],
|
||||||
domains: [],
|
domains: [],
|
||||||
resourceType: "Application",
|
resourceType: "Application",
|
||||||
@@ -179,6 +180,17 @@ class PermissionListPage extends BaseListPage {
|
|||||||
return Setting.getTags(text, "users");
|
return Setting.getTags(text, "users");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("role:Sub groups"),
|
||||||
|
dataIndex: "groups",
|
||||||
|
key: "groups",
|
||||||
|
// width: '100px',
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps("groups"),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return Setting.getTags(text, "groups");
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("role:Sub roles"),
|
title: i18next.t("role:Sub roles"),
|
||||||
dataIndex: "roles",
|
dataIndex: "roles",
|
||||||
|
@@ -13,8 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Button, Descriptions, Modal, Spin} from "antd";
|
import {Button, Descriptions, Spin} from "antd";
|
||||||
import {CheckCircleTwoTone} from "@ant-design/icons";
|
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import * as ProductBackend from "./backend/ProductBackend";
|
import * as ProductBackend from "./backend/ProductBackend";
|
||||||
import * as PlanBackend from "./backend/PlanBackend";
|
import * as PlanBackend from "./backend/PlanBackend";
|
||||||
@@ -36,7 +35,6 @@ class ProductBuyPage extends React.Component {
|
|||||||
pricing: props?.pricing ?? null,
|
pricing: props?.pricing ?? null,
|
||||||
plan: null,
|
plan: null,
|
||||||
isPlacingOrder: false,
|
isPlacingOrder: false,
|
||||||
qrCodeModalProvider: null,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,13 +128,6 @@ class ProductBuyPage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
buyProduct(product, provider) {
|
buyProduct(product, provider) {
|
||||||
if (provider.clientId.startsWith("http")) {
|
|
||||||
this.setState({
|
|
||||||
qrCodeModalProvider: provider,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
isPlacingOrder: true,
|
isPlacingOrder: true,
|
||||||
});
|
});
|
||||||
@@ -144,7 +135,11 @@ class ProductBuyPage extends React.Component {
|
|||||||
ProductBackend.buyProduct(product.owner, product.name, provider.name, this.state.pricingName ?? "", this.state.planName ?? "", this.state.userName ?? "")
|
ProductBackend.buyProduct(product.owner, product.name, provider.name, this.state.pricingName ?? "", this.state.planName ?? "", this.state.userName ?? "")
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
const payUrl = res.data;
|
const payment = res.data;
|
||||||
|
let payUrl = payment.payUrl;
|
||||||
|
if (provider.type === "WeChat Pay") {
|
||||||
|
payUrl = `/qrcode/${payment.owner}/${payment.name}?providerName=${provider.name}&payUrl=${encodeURI(payment.payUrl)}&successUrl=${encodeURI(payment.successUrl)}`;
|
||||||
|
}
|
||||||
Setting.goToLink(payUrl);
|
Setting.goToLink(payUrl);
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
|
||||||
@@ -159,45 +154,6 @@ class ProductBuyPage extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
renderQrCodeModal() {
|
|
||||||
if (this.state.qrCodeModalProvider === undefined || this.state.qrCodeModalProvider === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal title={
|
|
||||||
<div>
|
|
||||||
<CheckCircleTwoTone twoToneColor="rgb(45,120,213)" />
|
|
||||||
{" " + i18next.t("product:Please scan the QR code to pay")}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
open={this.state.qrCodeModalProvider !== undefined && this.state.qrCodeModalProvider !== null}
|
|
||||||
onOk={() => {
|
|
||||||
Setting.goToLink(this.state.product.returnUrl);
|
|
||||||
}}
|
|
||||||
onCancel={() => {
|
|
||||||
this.setState({
|
|
||||||
qrCodeModalProvider: null,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
okText={i18next.t("product:I have completed the payment")}
|
|
||||||
cancelText={i18next.t("general:Cancel")}>
|
|
||||||
<p key={this.state.qrCodeModalProvider?.name}>
|
|
||||||
{
|
|
||||||
i18next.t("product:Please provide your username in the remark")
|
|
||||||
}
|
|
||||||
:
|
|
||||||
{
|
|
||||||
Setting.getTag("default", this.props.account.name)
|
|
||||||
}
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<img src={this.state.qrCodeModalProvider?.clientId} alt={this.state.qrCodeModalProvider?.name} width={"472px"} style={{marginBottom: "20px"}} />
|
|
||||||
</p>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getPayButton(provider) {
|
getPayButton(provider) {
|
||||||
let text = provider.type;
|
let text = provider.type;
|
||||||
if (provider.type === "Dummy") {
|
if (provider.type === "Dummy") {
|
||||||
@@ -290,9 +246,6 @@ class ProductBuyPage extends React.Component {
|
|||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
</Descriptions>
|
</Descriptions>
|
||||||
</Spin>
|
</Spin>
|
||||||
{
|
|
||||||
this.renderQrCodeModal()
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -16,6 +16,8 @@ import React from "react";
|
|||||||
import {Button, Card, Checkbox, Col, Input, InputNumber, Row, Select, Switch} from "antd";
|
import {Button, Card, Checkbox, Col, Input, InputNumber, Row, Select, Switch} from "antd";
|
||||||
import {LinkOutlined} from "@ant-design/icons";
|
import {LinkOutlined} from "@ant-design/icons";
|
||||||
import * as ProviderBackend from "./backend/ProviderBackend";
|
import * as ProviderBackend from "./backend/ProviderBackend";
|
||||||
|
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||||
|
import * as CertBackend from "./backend/CertBackend";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import {authConfig} from "./auth/Auth";
|
import {authConfig} from "./auth/Auth";
|
||||||
@@ -24,7 +26,6 @@ import * as ProviderNotification from "./common/TestNotificationWidget";
|
|||||||
import * as ProviderEditTestSms from "./common/TestSmsWidget";
|
import * as ProviderEditTestSms from "./common/TestSmsWidget";
|
||||||
import copy from "copy-to-clipboard";
|
import copy from "copy-to-clipboard";
|
||||||
import {CaptchaPreview} from "./common/CaptchaPreview";
|
import {CaptchaPreview} from "./common/CaptchaPreview";
|
||||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
|
||||||
import {CountryCodeSelect} from "./common/select/CountryCodeSelect";
|
import {CountryCodeSelect} from "./common/select/CountryCodeSelect";
|
||||||
import * as Web3Auth from "./auth/Web3Auth";
|
import * as Web3Auth from "./auth/Web3Auth";
|
||||||
|
|
||||||
@@ -39,6 +40,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
providerName: props.match.params.providerName,
|
providerName: props.match.params.providerName,
|
||||||
owner: props.organizationName !== undefined ? props.organizationName : props.match.params.organizationName,
|
owner: props.organizationName !== undefined ? props.organizationName : props.match.params.organizationName,
|
||||||
provider: null,
|
provider: null,
|
||||||
|
certs: [],
|
||||||
organizations: [],
|
organizations: [],
|
||||||
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
||||||
};
|
};
|
||||||
@@ -47,6 +49,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
UNSAFE_componentWillMount() {
|
UNSAFE_componentWillMount() {
|
||||||
this.getOrganizations();
|
this.getOrganizations();
|
||||||
this.getProvider();
|
this.getProvider();
|
||||||
|
this.getCerts(this.state.owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
getProvider() {
|
getProvider() {
|
||||||
@@ -80,6 +83,17 @@ class ProviderEditPage extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCerts(owner) {
|
||||||
|
CertBackend.getCerts(owner)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
this.setState({
|
||||||
|
certs: res.data || [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
parseProviderField(key, value) {
|
parseProviderField(key, value) {
|
||||||
if (["port"].includes(key)) {
|
if (["port"].includes(key)) {
|
||||||
value = Setting.myParseInt(value);
|
value = Setting.myParseInt(value);
|
||||||
@@ -91,6 +105,11 @@ class ProviderEditPage extends React.Component {
|
|||||||
value = this.parseProviderField(key, value);
|
value = this.parseProviderField(key, value);
|
||||||
|
|
||||||
const provider = this.state.provider;
|
const provider = this.state.provider;
|
||||||
|
if (key === "owner" && provider["owner"] !== value) {
|
||||||
|
// the provider change the owner, reset the cert
|
||||||
|
provider["cert"] = "";
|
||||||
|
this.getCerts(value);
|
||||||
|
}
|
||||||
provider[key] = value;
|
provider[key] = value;
|
||||||
this.setState({
|
this.setState({
|
||||||
provider: provider,
|
provider: provider,
|
||||||
@@ -182,6 +201,12 @@ class ProviderEditPage extends React.Component {
|
|||||||
} else {
|
} else {
|
||||||
return Setting.getLabel(i18next.t("provider:Secret key"), i18next.t("provider:Secret key - Tooltip"));
|
return Setting.getLabel(i18next.t("provider:Secret key"), i18next.t("provider:Secret key - Tooltip"));
|
||||||
}
|
}
|
||||||
|
case "Notification":
|
||||||
|
if (provider.type === "Line") {
|
||||||
|
return Setting.getLabel(i18next.t("provider:Secret key"), i18next.t("provider:Secret key - Tooltip"));
|
||||||
|
} else {
|
||||||
|
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
|
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
|
||||||
}
|
}
|
||||||
@@ -272,12 +297,15 @@ class ProviderEditPage extends React.Component {
|
|||||||
tooltip = i18next.t("provider:Project Id - Tooltip");
|
tooltip = i18next.t("provider:Project Id - Tooltip");
|
||||||
}
|
}
|
||||||
} else if (provider.category === "Email") {
|
} else if (provider.category === "Email") {
|
||||||
if (provider.type === "SUBMAIL") {
|
if (provider.type === "SUBMAIL" || provider.type === "Azure ACS") {
|
||||||
text = i18next.t("provider:App ID");
|
text = i18next.t("provider:App ID");
|
||||||
tooltip = i18next.t("provider:App ID - Tooltip");
|
tooltip = i18next.t("provider:App ID - Tooltip");
|
||||||
}
|
}
|
||||||
} else if (provider.category === "Notification") {
|
} else if (provider.category === "Notification") {
|
||||||
if (provider.type === "Telegram") {
|
if (provider.type === "Viber") {
|
||||||
|
text = i18next.t("provider:Domain");
|
||||||
|
tooltip = i18next.t("provider:Domain - Tooltip");
|
||||||
|
} else if (provider.type === "Telegram" || provider.type === "DingTalk" || provider.type === "Pushover" || provider.type === "Pushbullet" || provider.type === "Slack" || provider.type === "Discord" || provider.type === "Line" || provider.type === "Matrix" || provider.type === "Rocket Chat") {
|
||||||
text = i18next.t("provider:App Key");
|
text = i18next.t("provider:App Key");
|
||||||
tooltip = i18next.t("provider:App Key - Tooltip");
|
tooltip = i18next.t("provider:App Key - Tooltip");
|
||||||
}
|
}
|
||||||
@@ -305,16 +333,25 @@ class ProviderEditPage extends React.Component {
|
|||||||
let text = "";
|
let text = "";
|
||||||
let tooltip = "";
|
let tooltip = "";
|
||||||
|
|
||||||
if (provider.type === "Telegram") {
|
if (provider.type === "Telegram" || provider.type === "Pushover" || provider.type === "Pushbullet" || provider.type === "Slack" || provider.type === "Discord" || provider.type === "Line" || provider.type === "Twitter" || provider.type === "Reddit" || provider.type === "Rocket Chat" || provider.type === "Viber") {
|
||||||
text = i18next.t("provider:Chat ID");
|
text = i18next.t("provider:Chat ID");
|
||||||
tooltip = i18next.t("provider:Chat ID - Tooltip");
|
tooltip = i18next.t("provider:Chat ID - Tooltip");
|
||||||
} else if (provider.type === "Custom HTTP") {
|
} else if (provider.type === "Custom HTTP" || provider.type === "Lark" || provider.type === "Microsoft Teams" || provider.type === "Webpush" || provider.type === "Matrix") {
|
||||||
text = i18next.t("provider:Endpoint");
|
text = i18next.t("provider:Endpoint");
|
||||||
tooltip = i18next.t("provider:Endpoint - Tooltip");
|
tooltip = i18next.t("provider:Endpoint - Tooltip");
|
||||||
|
} else if (provider.type === "DingTalk" || provider.type === "Bark") {
|
||||||
|
text = i18next.t("provider:Secret Key");
|
||||||
|
tooltip = i18next.t("provider:Secret Key - Tooltip");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (text === "" && tooltip === "") {
|
if (text === "" && tooltip === "") {
|
||||||
return null;
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel("Test Notification", "Test Notification")} :
|
||||||
|
</Col>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
@@ -406,7 +443,6 @@ class ProviderEditPage extends React.Component {
|
|||||||
this.updateProviderField("type", "Twilio SMS");
|
this.updateProviderField("type", "Twilio SMS");
|
||||||
} else if (value === "Storage") {
|
} else if (value === "Storage") {
|
||||||
this.updateProviderField("type", "AWS S3");
|
this.updateProviderField("type", "AWS S3");
|
||||||
this.updateProviderField("domain", Setting.getFullServerUrl());
|
|
||||||
} else if (value === "SAML") {
|
} else if (value === "SAML") {
|
||||||
this.updateProviderField("type", "Keycloak");
|
this.updateProviderField("type", "Keycloak");
|
||||||
} else if (value === "Payment") {
|
} else if (value === "Payment") {
|
||||||
@@ -590,19 +626,25 @@ class ProviderEditPage extends React.Component {
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
(this.state.provider.category === "Captcha" && this.state.provider.type === "Default") ||
|
(this.state.provider.category === "Captcha" && this.state.provider.type === "Default") ||
|
||||||
|
(this.state.provider.category === "Email" && this.state.provider.type === "Azure ACS") ||
|
||||||
(this.state.provider.category === "Web3") ||
|
(this.state.provider.category === "Web3") ||
|
||||||
(this.state.provider.category === "Storage" && this.state.provider.type === "Local File System" || (this.state.provider.category === "Notification")) ? null : (
|
(this.state.provider.category === "Storage" && this.state.provider.type === "Local File System" ||
|
||||||
|
(this.state.provider.category === "Notification" && this.state.provider.type !== "Webpush" && this.state.provider.type !== "Line" && this.state.provider.type !== "Matrix" && this.state.provider.type !== "Twitter" && this.state.provider.type !== "Reddit" && this.state.provider.type !== "Rocket Chat" && this.state.provider.type !== "Viber")) ? null : (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Row style={{marginTop: "20px"}} >
|
{
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
this.state.provider.type === "Line" ? null : (
|
||||||
{this.getClientIdLabel(this.state.provider)} :
|
<Row style={{marginTop: "20px"}} >
|
||||||
</Col>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
<Col span={22} >
|
{this.getClientIdLabel(this.state.provider)} :
|
||||||
<Input value={this.state.provider.clientId} onChange={e => {
|
</Col>
|
||||||
this.updateProviderField("clientId", e.target.value);
|
<Col span={22} >
|
||||||
}} />
|
<Input value={this.state.provider.clientId} onChange={e => {
|
||||||
</Col>
|
this.updateProviderField("clientId", e.target.value);
|
||||||
</Row>
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
}
|
||||||
<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}>
|
||||||
{this.getClientSecretLabel(this.state.provider)} :
|
{this.getClientSecretLabel(this.state.provider)} :
|
||||||
@@ -617,7 +659,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
this.state.provider.category !== "Email" && this.state.provider.type !== "WeChat" && this.state.provider.type !== "Aliyun Captcha" && this.state.provider.type !== "WeChat Pay" ? null : (
|
this.state.provider.category !== "Email" && this.state.provider.type !== "WeChat" && this.state.provider.type !== "Aliyun Captcha" && this.state.provider.type !== "WeChat Pay" && this.state.provider.type !== "Twitter" && this.state.provider.type !== "Reddit" ? null : (
|
||||||
<React.Fragment>
|
<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}>
|
||||||
@@ -630,7 +672,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
{
|
{
|
||||||
this.state.provider.type === "WeChat Pay" ? null : (
|
(this.state.provider.type === "WeChat Pay") || (this.state.provider.category === "Email" && this.state.provider.type === "Azure ACS") ? 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}>
|
||||||
{this.getClientSecret2Label(this.state.provider)} :
|
{this.getClientSecret2Label(this.state.provider)} :
|
||||||
@@ -783,6 +825,18 @@ class ProviderEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
) : null}
|
) : null}
|
||||||
|
{["Google Chat"].includes(this.state.provider.type) ? (
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("provider:Metadata"), i18next.t("provider:Metadata - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22}>
|
||||||
|
<TextArea rows={4} value={this.state.provider.metadata} onChange={e => {
|
||||||
|
this.updateProviderField("metadata", e.target.value);
|
||||||
|
}} />
|
||||||
|
</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:Content"), i18next.t("provider:Content - Tooltip"))} :
|
{Setting.getLabel(i18next.t("provider:Content"), i18next.t("provider:Content - Tooltip"))} :
|
||||||
@@ -813,26 +867,30 @@ class ProviderEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row style={{marginTop: "20px"}} >
|
{["Azure ACS"].includes(this.state.provider.type) ? null : (
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Row style={{marginTop: "20px"}} >
|
||||||
{Setting.getLabel(i18next.t("provider:Port"), i18next.t("provider:Port - Tooltip"))} :
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
</Col>
|
{Setting.getLabel(i18next.t("provider:Port"), i18next.t("provider:Port - Tooltip"))} :
|
||||||
<Col span={22} >
|
</Col>
|
||||||
<InputNumber value={this.state.provider.port} onChange={value => {
|
<Col span={22} >
|
||||||
this.updateProviderField("port", value);
|
<InputNumber value={this.state.provider.port} onChange={value => {
|
||||||
}} />
|
this.updateProviderField("port", value);
|
||||||
</Col>
|
}} />
|
||||||
</Row>
|
</Col>
|
||||||
<Row style={{marginTop: "20px"}} >
|
</Row>
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
)}
|
||||||
{Setting.getLabel(i18next.t("provider:Disable SSL"), i18next.t("provider:Disable SSL - Tooltip"))} :
|
{["Azure ACS"].includes(this.state.provider.type) ? null : (
|
||||||
</Col>
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col span={1} >
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
<Switch checked={this.state.provider.disableSsl} onChange={checked => {
|
{Setting.getLabel(i18next.t("provider:Disable SSL"), i18next.t("provider:Disable SSL - Tooltip"))} :
|
||||||
this.updateProviderField("disableSsl", checked);
|
</Col>
|
||||||
}} />
|
<Col span={1} >
|
||||||
</Col>
|
<Switch checked={this.state.provider.disableSsl} onChange={checked => {
|
||||||
</Row>
|
this.updateProviderField("disableSsl", checked);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
)}
|
||||||
<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"))} :
|
||||||
@@ -862,9 +920,11 @@ class ProviderEditPage extends React.Component {
|
|||||||
this.updateProviderField("receiver", e.target.value);
|
this.updateProviderField("receiver", e.target.value);
|
||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
<Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary" onClick={() => ProviderEditTestEmail.connectSmtpServer(this.state.provider)} >
|
{["Azure ACS"].includes(this.state.provider.type) ? null : (
|
||||||
{i18next.t("provider:Test SMTP Connection")}
|
<Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary" onClick={() => ProviderEditTestEmail.connectSmtpServer(this.state.provider)} >
|
||||||
</Button>
|
{i18next.t("provider:Test SMTP Connection")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
<Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary"
|
<Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary"
|
||||||
disabled={!Setting.isValidEmail(this.state.provider.receiver)}
|
disabled={!Setting.isValidEmail(this.state.provider.receiver)}
|
||||||
onClick={() => ProviderEditTestEmail.sendTestEmail(this.state.provider, this.state.provider.receiver)} >
|
onClick={() => ProviderEditTestEmail.sendTestEmail(this.state.provider, this.state.provider.receiver)} >
|
||||||
@@ -909,15 +969,15 @@ class ProviderEditPage extends React.Component {
|
|||||||
<Col span={4} >
|
<Col span={4} >
|
||||||
<Input.Group compact>
|
<Input.Group compact>
|
||||||
<CountryCodeSelect
|
<CountryCodeSelect
|
||||||
style={{width: "30%"}}
|
style={{width: "90px"}}
|
||||||
value={this.state.provider.content}
|
initValue={this.state.provider.content}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
this.updateProviderField("content", value);
|
this.updateProviderField("content", value);
|
||||||
}}
|
}}
|
||||||
countryCodes={this.props.account.organization.countryCodes}
|
countryCodes={this.props.account.organization.countryCodes}
|
||||||
/>
|
/>
|
||||||
<Input value={this.state.provider.receiver}
|
<Input value={this.state.provider.receiver}
|
||||||
style={{width: "70%"}}
|
style={{width: "150px"}}
|
||||||
placeholder = {i18next.t("user:Input your phone number")}
|
placeholder = {i18next.t("user:Input your phone number")}
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
this.updateProviderField("receiver", e.target.value);
|
this.updateProviderField("receiver", e.target.value);
|
||||||
@@ -1042,9 +1102,27 @@ class ProviderEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("general:Cert"), i18next.t("general:Cert - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Cert"), i18next.t("general:Cert - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Input value={this.state.provider.cert} onChange={e => {
|
<Select virtual={false} style={{width: "100%"}} value={this.state.provider.cert} onChange={(value => {this.updateProviderField("cert", value);})}>
|
||||||
this.updateProviderField("cert", e.target.value);
|
{
|
||||||
}} />
|
this.state.certs.map((cert, index) => <Option key={index} value={cert.name}>{cert.name}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
{
|
||||||
|
(this.state.provider.type === "Alipay") ? (
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Root Cert"), i18next.t("general:Root Cert - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: "100%"}} value={this.state.provider.metadata} onChange={(value => {this.updateProviderField("metadata", value);})}>
|
||||||
|
{
|
||||||
|
this.state.certs.map((cert, index) => <Option key={index} value={cert.name}>{cert.name}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
) : null
|
) : null
|
||||||
|
135
web/src/QrCodePage.js
Normal file
135
web/src/QrCodePage.js
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import QRCode from "qrcode.react";
|
||||||
|
import {Button, Col, Row} from "antd";
|
||||||
|
import * as PaymentBackend from "./backend/PaymentBackend";
|
||||||
|
import * as Setting from "./Setting";
|
||||||
|
import * as ProviderBackend from "./backend/ProviderBackend";
|
||||||
|
import i18next from "i18next";
|
||||||
|
|
||||||
|
class QrCodePage extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
this.state = {
|
||||||
|
classes: props,
|
||||||
|
owner: props.owner ?? (props.match?.params?.owner ?? null),
|
||||||
|
paymentName: props.paymentName ?? (props.match?.params?.paymentName ?? null),
|
||||||
|
providerName: props.providerName ?? params.get("providerName"),
|
||||||
|
payUrl: props.payUrl ?? params.get("payUrl"),
|
||||||
|
successUrl: props.successUrl ?? params.get("successUrl"),
|
||||||
|
provider: props.provider ?? null,
|
||||||
|
payment: null,
|
||||||
|
timer: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getProvider() {
|
||||||
|
if (!this.state.owner || !this.state.providerName) {
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await ProviderBackend.getProvider(this.state.owner, this.state.providerName);
|
||||||
|
if (res.status !== "ok") {
|
||||||
|
throw new Error(res.msg);
|
||||||
|
}
|
||||||
|
const provider = res.data;
|
||||||
|
this.setState({
|
||||||
|
provider: provider,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
Setting.showMessage("error", err.message);
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setNotifyTask() {
|
||||||
|
if (!this.state.owner || !this.state.paymentName) {
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
|
||||||
|
const notifyTask = async() => {
|
||||||
|
try {
|
||||||
|
const res = await PaymentBackend.notifyPayment(this.state.owner, this.state.paymentName);
|
||||||
|
if (res.status !== "ok") {
|
||||||
|
throw new Error(res.msg);
|
||||||
|
}
|
||||||
|
const payment = res.data;
|
||||||
|
if (payment.state !== "Created") {
|
||||||
|
Setting.goToLink(this.state.successUrl);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
Setting.showMessage("error", err.message);
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
timer: setTimeout(async() => {
|
||||||
|
await notifyTask();
|
||||||
|
this.setNotifyTask();
|
||||||
|
}, 2000),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
if (this.props.onUpdateApplication) {
|
||||||
|
this.props.onUpdateApplication(null);
|
||||||
|
}
|
||||||
|
this.getProvider();
|
||||||
|
this.setNotifyTask();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
clearInterval(this.state.timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderProviderInfo(provider) {
|
||||||
|
if (!provider) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const text = i18next.t(`product:${provider.type}`);
|
||||||
|
return (
|
||||||
|
<Button style={{height: "50px", borderWidth: "2px"}} shape="round" icon={
|
||||||
|
<img style={{marginRight: "10px"}} width={36} height={36} src={Setting.getProviderLogoURL(provider)} alt={provider.displayName} />
|
||||||
|
} size={"large"} >
|
||||||
|
{
|
||||||
|
text
|
||||||
|
}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (!this.state.payUrl || !this.state.successUrl || !this.state.owner || !this.state.paymentName) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="login-content">
|
||||||
|
<Col>
|
||||||
|
<Row style={{justifyContent: "center"}}>
|
||||||
|
{this.renderProviderInfo(this.state.provider)}
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "10px", justifyContent: "center"}}>
|
||||||
|
<QRCode value={this.state.payUrl} size={this.props.size ?? 200} />
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default QrCodePage;
|
@@ -161,6 +161,10 @@ export const OtherProviderInfo = {
|
|||||||
logo: `${StaticBaseUrl}/img/email_mailtrap.png`,
|
logo: `${StaticBaseUrl}/img/email_mailtrap.png`,
|
||||||
url: "https://mailtrap.io",
|
url: "https://mailtrap.io",
|
||||||
},
|
},
|
||||||
|
"Azure ACS": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_azure.png`,
|
||||||
|
url: "https://learn.microsoft.com/zh-cn/azure/communication-services",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Storage: {
|
Storage: {
|
||||||
"Local File System": {
|
"Local File System": {
|
||||||
@@ -283,6 +287,70 @@ export const OtherProviderInfo = {
|
|||||||
logo: `${StaticBaseUrl}/img/email_default.png`,
|
logo: `${StaticBaseUrl}/img/email_default.png`,
|
||||||
url: "https://casdoor.org/docs/provider/notification/overview",
|
url: "https://casdoor.org/docs/provider/notification/overview",
|
||||||
},
|
},
|
||||||
|
"DingTalk": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_dingtalk.png`,
|
||||||
|
url: "https://www.dingtalk.com/",
|
||||||
|
},
|
||||||
|
"Lark": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_lark.png`,
|
||||||
|
url: "https://www.larksuite.com/",
|
||||||
|
},
|
||||||
|
"Microsoft Teams": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_teams.png`,
|
||||||
|
url: "https://www.microsoft.com/microsoft-teams",
|
||||||
|
},
|
||||||
|
"Bark": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_bark.png`,
|
||||||
|
url: "https://apps.apple.com/us/app/bark-customed-notifications/id1403753865",
|
||||||
|
},
|
||||||
|
"Pushover": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_pushover.png`,
|
||||||
|
url: "https://pushover.net/",
|
||||||
|
},
|
||||||
|
"Pushbullet": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_pushbullet.png`,
|
||||||
|
url: "https://www.pushbullet.com/",
|
||||||
|
},
|
||||||
|
"Slack": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_slack.png`,
|
||||||
|
url: "https://slack.com/",
|
||||||
|
},
|
||||||
|
"Webpush": {
|
||||||
|
logo: `${StaticBaseUrl}/img/email_default.png`,
|
||||||
|
url: "https://developer.mozilla.org/en-US/docs/Web/API/Push_API",
|
||||||
|
},
|
||||||
|
"Discord": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_discord.png`,
|
||||||
|
url: "https://discord.com/",
|
||||||
|
},
|
||||||
|
"Google Chat": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_google_chat.png`,
|
||||||
|
url: "https://workspace.google.com/intl/en/products/chat/",
|
||||||
|
},
|
||||||
|
"Line": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_line.png`,
|
||||||
|
url: "https://line.me/",
|
||||||
|
},
|
||||||
|
"Matrix": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_matrix.png`,
|
||||||
|
url: "https://www.matrix.org/",
|
||||||
|
},
|
||||||
|
"Twitter": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_twitter.png`,
|
||||||
|
url: "https://twitter.com/",
|
||||||
|
},
|
||||||
|
"Reddit": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_reddit.png`,
|
||||||
|
url: "https://www.reddit.com/",
|
||||||
|
},
|
||||||
|
"Rocket Chat": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_rocket_chat.png`,
|
||||||
|
url: "https://rocket.chat/",
|
||||||
|
},
|
||||||
|
"Viber": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_viber.png`,
|
||||||
|
url: "https://www.viber.com/",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -908,6 +976,7 @@ export function getProviderTypeOptions(category) {
|
|||||||
{id: "Default", name: "Default"},
|
{id: "Default", name: "Default"},
|
||||||
{id: "SUBMAIL", name: "SUBMAIL"},
|
{id: "SUBMAIL", name: "SUBMAIL"},
|
||||||
{id: "Mailtrap", name: "Mailtrap"},
|
{id: "Mailtrap", name: "Mailtrap"},
|
||||||
|
{id: "Azure ACS", name: "Azure ACS"},
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
} else if (category === "SMS") {
|
} else if (category === "SMS") {
|
||||||
@@ -973,6 +1042,22 @@ export function getProviderTypeOptions(category) {
|
|||||||
return ([
|
return ([
|
||||||
{id: "Telegram", name: "Telegram"},
|
{id: "Telegram", name: "Telegram"},
|
||||||
{id: "Custom HTTP", name: "Custom HTTP"},
|
{id: "Custom HTTP", name: "Custom HTTP"},
|
||||||
|
{id: "DingTalk", name: "DingTalk"},
|
||||||
|
{id: "Lark", name: "Lark"},
|
||||||
|
{id: "Microsoft Teams", name: "Microsoft Teams"},
|
||||||
|
{id: "Bark", name: "Bark"},
|
||||||
|
{id: "Pushover", name: "Pushover"},
|
||||||
|
{id: "Pushbullet", name: "Pushbullet"},
|
||||||
|
{id: "Slack", name: "Slack"},
|
||||||
|
{id: "Webpush", name: "Webpush"},
|
||||||
|
{id: "Discord", name: "Discord"},
|
||||||
|
{id: "Google Chat", name: "Google Chat"},
|
||||||
|
{id: "Line", name: "Line"},
|
||||||
|
{id: "Matrix", name: "Matrix"},
|
||||||
|
{id: "Twitter", name: "Twitter"},
|
||||||
|
{id: "Reddit", name: "Reddit"},
|
||||||
|
{id: "Rocket Chat", name: "Rocket Chat"},
|
||||||
|
{id: "Viber", name: "Viber"},
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
return [];
|
return [];
|
||||||
|
@@ -178,7 +178,7 @@ class SystemInfo extends React.Component {
|
|||||||
<Col span={6}></Col>
|
<Col span={6}></Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Tour
|
<Tour
|
||||||
open={this.state.isTourVisible}
|
open={Setting.isMobile() ? false : this.state.isTourVisible}
|
||||||
onClose={this.setIsTourVisible}
|
onClose={this.setIsTourVisible}
|
||||||
steps={this.getSteps()}
|
steps={this.getSteps()}
|
||||||
indicatorsRender={(current, total) => (
|
indicatorsRender={(current, total) => (
|
||||||
|
@@ -182,6 +182,8 @@ class SignupPage extends React.Component {
|
|||||||
AuthBackend.signup(values)
|
AuthBackend.signup(values)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
|
// the user's id will be returned by `signup()`, if user signup by phone, the `username` in `values` is undefined.
|
||||||
|
values.username = res.data.split("/")[1];
|
||||||
if (Setting.hasPromptPage(application) && (!values.plan || !values.pricing)) {
|
if (Setting.hasPromptPage(application) && (!values.plan || !values.pricing)) {
|
||||||
AuthBackend.getAccount("")
|
AuthBackend.getAccount("")
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
|
@@ -303,7 +303,7 @@ export function initWeb3Onboard(application, provider) {
|
|||||||
description: "Connect a wallet using Casdoor",
|
description: "Connect a wallet using Casdoor",
|
||||||
recommendedInjectedWallets: [
|
recommendedInjectedWallets: [
|
||||||
{name: "MetaMask", url: "https://metamask.io"},
|
{name: "MetaMask", url: "https://metamask.io"},
|
||||||
{name: "Coinbase", url: "https://wallet.coinbase.com/"},
|
{name: "Coinbase", url: "https://www.coinbase.com/wallet"},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -50,11 +50,15 @@ window.fetch = async(url, option = {}) => {
|
|||||||
requestFilters.forEach(filter => filter(url, option));
|
requestFilters.forEach(filter => filter(url, option));
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
originalFetch(url, option).then(res => {
|
originalFetch(url, option)
|
||||||
if (!url.startsWith("/api/get-organizations")) {
|
.then(res => {
|
||||||
responseFilters.forEach(filter => filter(res.clone()));
|
if (!url.startsWith("/api/get-organizations")) {
|
||||||
}
|
responseFilters.forEach(filter => filter(res.clone()));
|
||||||
resolve(res);
|
}
|
||||||
});
|
resolve(res);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@@ -105,8 +105,8 @@ const Dashboard = (props) => {
|
|||||||
i18next.t("general:Applications"),
|
i18next.t("general:Applications"),
|
||||||
i18next.t("general:Organizations"),
|
i18next.t("general:Organizations"),
|
||||||
i18next.t("general:Subscriptions"),
|
i18next.t("general:Subscriptions"),
|
||||||
]},
|
], top: "10%"},
|
||||||
grid: {left: "3%", right: "4%", bottom: "3%", containLabel: true},
|
grid: {left: "3%", right: "4%", bottom: "0", top: "25%", containLabel: true},
|
||||||
xAxis: {type: "category", boundaryGap: false, data: dateArray},
|
xAxis: {type: "category", boundaryGap: false, data: dateArray},
|
||||||
yAxis: {type: "value"},
|
yAxis: {type: "value"},
|
||||||
series: [
|
series: [
|
||||||
@@ -120,24 +120,24 @@ const Dashboard = (props) => {
|
|||||||
myChart.setOption(option);
|
myChart.setOption(option);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row id="statistic" gutter={80}>
|
<Row id="statistic" gutter={80} justify={"center"}>
|
||||||
<Col span={50}>
|
<Col span={50} style={{marginBottom: "10px"}}>
|
||||||
<Card bordered={false} bodyStyle={{width: "100%", height: "150px", display: "flex", alignItems: "center", justifyContent: "center"}}>
|
<Card bordered={false} bodyStyle={{width: Setting.isMobile() ? "340px" : "100%", height: Setting.isMobile() ? "100px" : "150px", display: "flex", alignItems: "center", justifyContent: "center"}}>
|
||||||
<Statistic title={i18next.t("home:Total users")} fontSize="100px" value={dashboardData.userCounts[30]} valueStyle={{fontSize: "30px"}} style={{width: "200px", paddingLeft: "10px"}} />
|
<Statistic title={i18next.t("home:Total users")} fontSize="100px" value={dashboardData.userCounts[30]} valueStyle={{fontSize: "30px"}} style={{width: "200px", paddingLeft: "10px"}} />
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={50}>
|
<Col span={50} style={{marginBottom: "10px"}}>
|
||||||
<Card bordered={false} bodyStyle={{width: "100%", height: "150px", display: "flex", alignItems: "center", justifyContent: "center"}}>
|
<Card bordered={false} bodyStyle={{width: Setting.isMobile() ? "340px" : "100%", height: Setting.isMobile() ? "100px" : "150px", display: "flex", alignItems: "center", justifyContent: "center"}}>
|
||||||
<Statistic title={i18next.t("home:New users today")} fontSize="100px" value={dashboardData.userCounts[30] - dashboardData.userCounts[30 - 1]} valueStyle={{fontSize: "30px"}} prefix={<ArrowUpOutlined />} style={{width: "200px", paddingLeft: "10px"}} />
|
<Statistic title={i18next.t("home:New users today")} fontSize="100px" value={dashboardData.userCounts[30] - dashboardData.userCounts[30 - 1]} valueStyle={{fontSize: "30px"}} prefix={<ArrowUpOutlined />} style={{width: "200px", paddingLeft: "10px"}} />
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={50}>
|
<Col span={50} style={{marginBottom: "10px"}}>
|
||||||
<Card bordered={false} bodyStyle={{width: "100%", height: "150px", display: "flex", alignItems: "center", justifyContent: "center"}}>
|
<Card bordered={false} bodyStyle={{width: Setting.isMobile() ? "340px" : "100%", height: Setting.isMobile() ? "100px" : "150px", display: "flex", alignItems: "center", justifyContent: "center"}}>
|
||||||
<Statistic title={i18next.t("home:New users past 7 days")} value={dashboardData.userCounts[30] - dashboardData.userCounts[30 - 7]} valueStyle={{fontSize: "30px"}} prefix={<ArrowUpOutlined />} style={{width: "200px", paddingLeft: "10px"}} />
|
<Statistic title={i18next.t("home:New users past 7 days")} value={dashboardData.userCounts[30] - dashboardData.userCounts[30 - 7]} valueStyle={{fontSize: "30px"}} prefix={<ArrowUpOutlined />} style={{width: "200px", paddingLeft: "10px"}} />
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={50}>
|
<Col span={50} style={{marginBottom: "10px"}}>
|
||||||
<Card bordered={false} bodyStyle={{width: "100%", height: "150px", display: "flex", alignItems: "center", justifyContent: "center"}}>
|
<Card bordered={false} bodyStyle={{width: Setting.isMobile() ? "340px" : "100%", height: Setting.isMobile() ? "100px" : "150px", display: "flex", alignItems: "center", justifyContent: "center"}}>
|
||||||
<Statistic title={i18next.t("home:New users past 30 days")} value={dashboardData.userCounts[30] - dashboardData.userCounts[30 - 30]} valueStyle={{fontSize: "30px"}} prefix={<ArrowUpOutlined />} style={{width: "200px", paddingLeft: "10px"}} />
|
<Statistic title={i18next.t("home:New users past 30 days")} value={dashboardData.userCounts[30] - dashboardData.userCounts[30 - 30]} valueStyle={{fontSize: "30px"}} prefix={<ArrowUpOutlined />} style={{width: "200px", paddingLeft: "10px"}} />
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
@@ -150,7 +150,7 @@ const Dashboard = (props) => {
|
|||||||
{renderEChart()}
|
{renderEChart()}
|
||||||
<div id="echarts-chart" style={{width: "80%", height: "400px", textAlign: "center", marginTop: "20px"}} />
|
<div id="echarts-chart" style={{width: "80%", height: "400px", textAlign: "center", marginTop: "20px"}} />
|
||||||
<Tour
|
<Tour
|
||||||
open={isTourVisible}
|
open={Setting.isMobile() ? false : isTourVisible}
|
||||||
onClose={setIsTourToLocal}
|
onClose={setIsTourToLocal}
|
||||||
steps={getSteps()}
|
steps={getSteps()}
|
||||||
indicatorsRender={(current, total) => (
|
indicatorsRender={(current, total) => (
|
||||||
|
@@ -49,7 +49,11 @@ export const AgreementModal = (props) => {
|
|||||||
function getTermsOfUseContent(url) {
|
function getTermsOfUseContent(url) {
|
||||||
return fetch(url, {
|
return fetch(url, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
}).then(r => r.text());
|
})
|
||||||
|
.then(r => r.text())
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to get TermsOfUse URL")}: ${url}, ${error}`);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isAgreementRequired(application) {
|
export function isAgreementRequired(application) {
|
||||||
|
Reference in New Issue
Block a user