mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-15 17:23:49 +08:00
Compare commits
53 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
559a91e8ee | |||
b0aaf09ef1 | |||
7e2f67c49a | |||
e584a6a111 | |||
6700d2e244 | |||
0c5c308071 | |||
0b859197da | |||
3078409343 | |||
bbf2db2e00 | |||
0c7b911ce7 | |||
2cc55715ac | |||
c829bf1769 | |||
ec956c12ca | |||
d3d4646c56 | |||
669ac7c618 | |||
6715efd781 | |||
953be4a7b6 | |||
943cc43427 | |||
1e5ce7a045 |
@ -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
|
||||||
}
|
}
|
||||||
|
@ -62,13 +62,13 @@ func (c *ApiController) GetApplications() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||||
app, err := object.GetPaginationApplications(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
application, err := object.GetPaginationApplications(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
applications := object.GetMaskedApplications(app, userId)
|
applications := object.GetMaskedApplications(application, userId)
|
||||||
c.ResponseOk(applications, paginator.Nums())
|
c.ResponseOk(applications, paginator.Nums())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -84,13 +84,33 @@ func (c *ApiController) GetApplication() {
|
|||||||
userId := c.GetSessionUsername()
|
userId := c.GetSessionUsername()
|
||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
app, err := object.GetApplication(id)
|
application, err := object.GetApplication(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.ResponseOk(object.GetMaskedApplication(app, userId))
|
if c.Input().Get("withKey") != "" && application != nil && application.Cert != "" {
|
||||||
|
cert, err := object.GetCert(util.GetId(application.Owner, application.Cert))
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserApplication
|
// GetUserApplication
|
||||||
@ -164,13 +184,13 @@ func (c *ApiController) GetOrganizationApplications() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||||
app, err := object.GetPaginationOrganizationApplications(owner, organization, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
application, err := object.GetPaginationOrganizationApplications(owner, organization, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
applications := object.GetMaskedApplications(app, userId)
|
applications := object.GetMaskedApplications(application, userId)
|
||||||
c.ResponseOk(applications, paginator.Nums())
|
c.ResponseOk(applications, paginator.Nums())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -35,6 +35,11 @@ const (
|
|||||||
UnauthorizedService string = "UNAUTHORIZED_SERVICE"
|
UnauthorizedService string = "UNAUTHORIZED_SERVICE"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func queryUnescape(service string) string {
|
||||||
|
s, _ := url.QueryUnescape(service)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
func (c *RootController) CasValidate() {
|
func (c *RootController) CasValidate() {
|
||||||
ticket := c.Input().Get("ticket")
|
ticket := c.Input().Get("ticket")
|
||||||
service := c.Input().Get("service")
|
service := c.Input().Get("service")
|
||||||
@ -60,24 +65,25 @@ func (c *RootController) CasServiceValidate() {
|
|||||||
if !strings.HasPrefix(ticket, "ST") {
|
if !strings.HasPrefix(ticket, "ST") {
|
||||||
c.sendCasAuthenticationResponseErr(InvalidTicket, fmt.Sprintf("Ticket %s not recognized", ticket), format)
|
c.sendCasAuthenticationResponseErr(InvalidTicket, fmt.Sprintf("Ticket %s not recognized", ticket), format)
|
||||||
}
|
}
|
||||||
c.CasP3ServiceAndProxyValidate()
|
c.CasP3ProxyValidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RootController) CasProxyValidate() {
|
func (c *RootController) CasProxyValidate() {
|
||||||
|
// https://apereo.github.io/cas/6.6.x/protocol/CAS-Protocol-Specification.html#26-proxyvalidate-cas-20
|
||||||
|
// "/proxyValidate" should accept both service tickets and proxy tickets.
|
||||||
|
c.CasP3ProxyValidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RootController) CasP3ServiceValidate() {
|
||||||
ticket := c.Input().Get("ticket")
|
ticket := c.Input().Get("ticket")
|
||||||
format := c.Input().Get("format")
|
format := c.Input().Get("format")
|
||||||
if !strings.HasPrefix(ticket, "PT") {
|
if !strings.HasPrefix(ticket, "ST") {
|
||||||
c.sendCasAuthenticationResponseErr(InvalidTicket, fmt.Sprintf("Ticket %s not recognized", ticket), format)
|
c.sendCasAuthenticationResponseErr(InvalidTicket, fmt.Sprintf("Ticket %s not recognized", ticket), format)
|
||||||
}
|
}
|
||||||
c.CasP3ServiceAndProxyValidate()
|
c.CasP3ProxyValidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
func queryUnescape(service string) string {
|
func (c *RootController) CasP3ProxyValidate() {
|
||||||
s, _ := url.QueryUnescape(service)
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RootController) CasP3ServiceAndProxyValidate() {
|
|
||||||
ticket := c.Input().Get("ticket")
|
ticket := c.Input().Get("ticket")
|
||||||
format := c.Input().Get("format")
|
format := c.Input().Get("format")
|
||||||
service := c.Input().Get("service")
|
service := c.Input().Get("service")
|
||||||
@ -115,15 +121,17 @@ func (c *RootController) CasP3ServiceAndProxyValidate() {
|
|||||||
pgtiou := serviceResponse.Success.ProxyGrantingTicket
|
pgtiou := serviceResponse.Success.ProxyGrantingTicket
|
||||||
// todo: check whether it is https
|
// todo: check whether it is https
|
||||||
pgtUrlObj, err := url.Parse(pgtUrl)
|
pgtUrlObj, err := url.Parse(pgtUrl)
|
||||||
|
if err != nil {
|
||||||
|
c.sendCasAuthenticationResponseErr(InvalidProxyCallback, err.Error(), format)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if pgtUrlObj.Scheme != "https" {
|
if pgtUrlObj.Scheme != "https" {
|
||||||
c.sendCasAuthenticationResponseErr(InvalidProxyCallback, "callback is not https", format)
|
c.sendCasAuthenticationResponseErr(InvalidProxyCallback, "callback is not https", format)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// make a request to pgturl passing pgt and pgtiou
|
// make a request to pgturl passing pgt and pgtiou
|
||||||
if err != nil {
|
|
||||||
c.sendCasAuthenticationResponseErr(InternalError, err.Error(), format)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
param := pgtUrlObj.Query()
|
param := pgtUrlObj.Query()
|
||||||
param.Add("pgtId", pgt)
|
param.Add("pgtId", pgt)
|
||||||
param.Add("pgtIou", pgtiou)
|
param.Add("pgtIou", pgtiou)
|
||||||
@ -263,7 +271,6 @@ func (c *RootController) sendCasAuthenticationResponseErr(code, msg, format stri
|
|||||||
Message: msg,
|
Message: msg,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if format == "json" {
|
if format == "json" {
|
||||||
c.Data["json"] = serviceResponse
|
c.Data["json"] = serviceResponse
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
|
@ -83,7 +83,7 @@ func (c *ApiController) GetEnforcer() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if loadModelCfg == "true" {
|
if loadModelCfg == "true" && enforcer.Model != "" {
|
||||||
err := enforcer.LoadModelCfg()
|
err := enforcer.LoadModelCfg()
|
||||||
if err != nil {
|
if err != 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
|
||||||
|
@ -114,7 +114,7 @@ func (c *ApiController) GetPlan() {
|
|||||||
// @router /update-plan [post]
|
// @router /update-plan [post]
|
||||||
func (c *ApiController) UpdatePlan() {
|
func (c *ApiController) UpdatePlan() {
|
||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
|
owner := util.GetOwnerFromId(id)
|
||||||
var plan object.Plan
|
var plan object.Plan
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &plan)
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &plan)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -122,17 +122,19 @@ func (c *ApiController) UpdatePlan() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if plan.Product != "" {
|
if plan.Product != "" {
|
||||||
planId := util.GetId(plan.Owner, plan.Product)
|
productId := util.GetId(owner, plan.Product)
|
||||||
product, err := object.GetProduct(planId)
|
product, err := object.GetProduct(productId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
object.UpdateProductForPlan(&plan, product)
|
if product != nil {
|
||||||
_, err = object.UpdateProduct(planId, product)
|
object.UpdateProductForPlan(&plan, product)
|
||||||
if err != nil {
|
_, err = object.UpdateProduct(productId, product)
|
||||||
c.ResponseError(err.Error())
|
if err != nil {
|
||||||
return
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.Data["json"] = wrapActionResponse(object.UpdatePlan(id, &plan))
|
c.Data["json"] = wrapActionResponse(object.UpdatePlan(id, &plan))
|
||||||
|
@ -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")
|
||||||
|
@ -258,6 +258,13 @@ func (c *ApiController) UpdateUser() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.Input().Get("allowEmpty") == "" {
|
||||||
|
if user.DisplayName == "" {
|
||||||
|
c.ResponseError(c.T("user:Display name cannot be empty"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if msg := object.CheckUpdateUser(oldUser, &user, c.GetAcceptLanguage()); msg != "" {
|
if msg := object.CheckUpdateUser(oldUser, &user, c.GetAcceptLanguage()); msg != "" {
|
||||||
c.ResponseError(msg)
|
c.ResponseError(msg)
|
||||||
return
|
return
|
||||||
@ -441,12 +448,25 @@ func (c *ApiController) SetPassword() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
targetUser, err := object.GetUser(userId)
|
targetUser, err := object.GetUser(userId)
|
||||||
|
if targetUser == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), userId))
|
||||||
|
return
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
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
|
||||||
)
|
)
|
||||||
|
@ -38,7 +38,13 @@ func TestGenerateI18nFrontend(t *testing.T) {
|
|||||||
applyToOtherLanguage("frontend", "tr", data)
|
applyToOtherLanguage("frontend", "tr", data)
|
||||||
applyToOtherLanguage("frontend", "ar", data)
|
applyToOtherLanguage("frontend", "ar", data)
|
||||||
applyToOtherLanguage("frontend", "he", data)
|
applyToOtherLanguage("frontend", "he", data)
|
||||||
|
applyToOtherLanguage("frontend", "nl", data)
|
||||||
|
applyToOtherLanguage("frontend", "pl", data)
|
||||||
applyToOtherLanguage("frontend", "fi", data)
|
applyToOtherLanguage("frontend", "fi", data)
|
||||||
|
applyToOtherLanguage("frontend", "sv", data)
|
||||||
|
applyToOtherLanguage("frontend", "uk", data)
|
||||||
|
applyToOtherLanguage("frontend", "kk", data)
|
||||||
|
applyToOtherLanguage("frontend", "fa", data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGenerateI18nBackend(t *testing.T) {
|
func TestGenerateI18nBackend(t *testing.T) {
|
||||||
@ -60,5 +66,11 @@ func TestGenerateI18nBackend(t *testing.T) {
|
|||||||
applyToOtherLanguage("backend", "tr", data)
|
applyToOtherLanguage("backend", "tr", data)
|
||||||
applyToOtherLanguage("backend", "ar", data)
|
applyToOtherLanguage("backend", "ar", data)
|
||||||
applyToOtherLanguage("backend", "he", data)
|
applyToOtherLanguage("backend", "he", data)
|
||||||
|
applyToOtherLanguage("backend", "nl", data)
|
||||||
|
applyToOtherLanguage("backend", "pl", data)
|
||||||
applyToOtherLanguage("backend", "fi", data)
|
applyToOtherLanguage("backend", "fi", data)
|
||||||
|
applyToOtherLanguage("backend", "sv", data)
|
||||||
|
applyToOtherLanguage("backend", "uk", data)
|
||||||
|
applyToOtherLanguage("backend", "kk", data)
|
||||||
|
applyToOtherLanguage("backend", "fa", data)
|
||||||
}
|
}
|
||||||
|
142
i18n/locales/fa/data.json
Normal file
142
i18n/locales/fa/data.json
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
{
|
||||||
|
"account": {
|
||||||
|
"Failed to add user": "Failed to add user",
|
||||||
|
"Get init score failed, error: %w": "Get init score failed, error: %w",
|
||||||
|
"Please sign out first": "Please sign out first",
|
||||||
|
"The application does not allow to sign up new account": "The application does not allow to sign up new account"
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"Challenge method should be S256": "Challenge method should be S256",
|
||||||
|
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s",
|
||||||
|
"Failed to login in: %s": "Failed to login in: %s",
|
||||||
|
"Invalid token": "Invalid token",
|
||||||
|
"State expected: %s, but got: %s": "State expected: %s, but got: %s",
|
||||||
|
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up",
|
||||||
|
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
|
||||||
|
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
|
||||||
|
"The application: %s does not exist": "The application: %s does not exist",
|
||||||
|
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
|
||||||
|
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||||
|
"Unauthorized operation": "Unauthorized operation",
|
||||||
|
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
|
||||||
|
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
|
||||||
|
},
|
||||||
|
"cas": {
|
||||||
|
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||||
|
},
|
||||||
|
"check": {
|
||||||
|
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||||
|
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||||
|
"DisplayName is not valid real name": "DisplayName is not valid real name",
|
||||||
|
"Email already exists": "Email already exists",
|
||||||
|
"Email cannot be empty": "Email cannot be empty",
|
||||||
|
"Email is invalid": "Email is invalid",
|
||||||
|
"Empty username.": "Empty username.",
|
||||||
|
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||||
|
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||||
|
"LastName cannot be blank": "LastName cannot be blank",
|
||||||
|
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||||
|
"Organization does not exist": "Organization does not exist",
|
||||||
|
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||||
|
"Phone already exists": "Phone already exists",
|
||||||
|
"Phone cannot be empty": "Phone cannot be empty",
|
||||||
|
"Phone number is invalid": "Phone number is invalid",
|
||||||
|
"Session outdated, please login again": "Session outdated, please login again",
|
||||||
|
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
|
||||||
|
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||||
|
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.",
|
||||||
|
"Username already exists": "Username already exists",
|
||||||
|
"Username cannot be an email address": "Username cannot be an email address",
|
||||||
|
"Username cannot contain white spaces": "Username cannot contain white spaces",
|
||||||
|
"Username cannot start with a digit": "Username cannot start with a digit",
|
||||||
|
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
|
||||||
|
"Username must have at least 2 characters": "Username must have at least 2 characters",
|
||||||
|
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
|
||||||
|
"Your region is not allow to signup by phone": "Your region is not allow to signup by phone",
|
||||||
|
"password or code is incorrect": "password or code is incorrect",
|
||||||
|
"password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
|
||||||
|
"unsupported password type: %s": "unsupported password type: %s"
|
||||||
|
},
|
||||||
|
"general": {
|
||||||
|
"Missing parameter": "Missing parameter",
|
||||||
|
"Please login first": "Please login first",
|
||||||
|
"The user: %s doesn't exist": "The user: %s doesn't exist",
|
||||||
|
"don't support captchaProvider: ": "don't support captchaProvider: ",
|
||||||
|
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
|
||||||
|
},
|
||||||
|
"ldap": {
|
||||||
|
"Ldap server exist": "Ldap server exist"
|
||||||
|
},
|
||||||
|
"link": {
|
||||||
|
"Please link first": "Please link first",
|
||||||
|
"This application has no providers": "This application has no providers",
|
||||||
|
"This application has no providers of type": "This application has no providers of type",
|
||||||
|
"This provider can't be unlinked": "This provider can't be unlinked",
|
||||||
|
"You are not the global admin, you can't unlink other users": "You are not the global admin, you can't unlink other users",
|
||||||
|
"You can't unlink yourself, you are not a member of any application": "You can't unlink yourself, you are not a member of any application"
|
||||||
|
},
|
||||||
|
"organization": {
|
||||||
|
"Only admin can modify the %s.": "Only admin can modify the %s.",
|
||||||
|
"The %s is immutable.": "The %s is immutable.",
|
||||||
|
"Unknown modify rule %s.": "Unknown modify rule %s."
|
||||||
|
},
|
||||||
|
"provider": {
|
||||||
|
"Invalid application id": "Invalid application id",
|
||||||
|
"the provider: %s does not exist": "the provider: %s does not exist"
|
||||||
|
},
|
||||||
|
"resource": {
|
||||||
|
"User is nil for tag: avatar": "User is nil for tag: avatar",
|
||||||
|
"Username or fullFilePath is empty: username = %s, fullFilePath = %s": "Username or fullFilePath is empty: username = %s, fullFilePath = %s"
|
||||||
|
},
|
||||||
|
"saml": {
|
||||||
|
"Application %s not found": "Application %s not found"
|
||||||
|
},
|
||||||
|
"saml_sp": {
|
||||||
|
"provider %s's category is not SAML": "provider %s's category is not SAML"
|
||||||
|
},
|
||||||
|
"service": {
|
||||||
|
"Empty parameters for emailForm: %v": "Empty parameters for emailForm: %v",
|
||||||
|
"Invalid Email receivers: %s": "Invalid Email receivers: %s",
|
||||||
|
"Invalid phone receivers: %s": "Invalid phone receivers: %s"
|
||||||
|
},
|
||||||
|
"storage": {
|
||||||
|
"The objectKey: %s is not allowed": "The objectKey: %s is not allowed",
|
||||||
|
"The provider type: %s is not supported": "The provider type: %s is not supported"
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"Empty clientId or clientSecret": "Empty clientId or clientSecret",
|
||||||
|
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application",
|
||||||
|
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret",
|
||||||
|
"Invalid client_id": "Invalid client_id",
|
||||||
|
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list",
|
||||||
|
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"Display name cannot be empty": "Display name cannot be empty",
|
||||||
|
"New password cannot contain blank space.": "New password cannot contain blank space."
|
||||||
|
},
|
||||||
|
"user_upload": {
|
||||||
|
"Failed to import users": "Failed to import users"
|
||||||
|
},
|
||||||
|
"util": {
|
||||||
|
"No application is found for userId: %s": "No application is found for userId: %s",
|
||||||
|
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
||||||
|
"The provider: %s is not found": "The provider: %s is not found"
|
||||||
|
},
|
||||||
|
"verification": {
|
||||||
|
"Code has not been sent yet!": "Code has not been sent yet!",
|
||||||
|
"Invalid captcha provider.": "Invalid captcha provider.",
|
||||||
|
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
|
||||||
|
"Turing test failed.": "Turing test failed.",
|
||||||
|
"Unable to get the email modify rule.": "Unable to get the email modify rule.",
|
||||||
|
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
|
||||||
|
"Unknown type": "Unknown type",
|
||||||
|
"Wrong verification code!": "Wrong verification code!",
|
||||||
|
"You should verify your code in %d min!": "You should verify your code in %d min!",
|
||||||
|
"the user does not exist, please sign up first": "the user does not exist, please sign up first"
|
||||||
|
},
|
||||||
|
"webauthn": {
|
||||||
|
"Found no credentials for this user": "Found no credentials for this user",
|
||||||
|
"Please call WebAuthnSigninBegin first": "Please call WebAuthnSigninBegin first"
|
||||||
|
}
|
||||||
|
}
|
142
i18n/locales/kk/data.json
Normal file
142
i18n/locales/kk/data.json
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
{
|
||||||
|
"account": {
|
||||||
|
"Failed to add user": "Failed to add user",
|
||||||
|
"Get init score failed, error: %w": "Get init score failed, error: %w",
|
||||||
|
"Please sign out first": "Please sign out first",
|
||||||
|
"The application does not allow to sign up new account": "The application does not allow to sign up new account"
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"Challenge method should be S256": "Challenge method should be S256",
|
||||||
|
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s",
|
||||||
|
"Failed to login in: %s": "Failed to login in: %s",
|
||||||
|
"Invalid token": "Invalid token",
|
||||||
|
"State expected: %s, but got: %s": "State expected: %s, but got: %s",
|
||||||
|
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up",
|
||||||
|
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
|
||||||
|
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
|
||||||
|
"The application: %s does not exist": "The application: %s does not exist",
|
||||||
|
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
|
||||||
|
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||||
|
"Unauthorized operation": "Unauthorized operation",
|
||||||
|
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
|
||||||
|
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
|
||||||
|
},
|
||||||
|
"cas": {
|
||||||
|
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||||
|
},
|
||||||
|
"check": {
|
||||||
|
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||||
|
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||||
|
"DisplayName is not valid real name": "DisplayName is not valid real name",
|
||||||
|
"Email already exists": "Email already exists",
|
||||||
|
"Email cannot be empty": "Email cannot be empty",
|
||||||
|
"Email is invalid": "Email is invalid",
|
||||||
|
"Empty username.": "Empty username.",
|
||||||
|
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||||
|
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||||
|
"LastName cannot be blank": "LastName cannot be blank",
|
||||||
|
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||||
|
"Organization does not exist": "Organization does not exist",
|
||||||
|
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||||
|
"Phone already exists": "Phone already exists",
|
||||||
|
"Phone cannot be empty": "Phone cannot be empty",
|
||||||
|
"Phone number is invalid": "Phone number is invalid",
|
||||||
|
"Session outdated, please login again": "Session outdated, please login again",
|
||||||
|
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
|
||||||
|
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||||
|
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.",
|
||||||
|
"Username already exists": "Username already exists",
|
||||||
|
"Username cannot be an email address": "Username cannot be an email address",
|
||||||
|
"Username cannot contain white spaces": "Username cannot contain white spaces",
|
||||||
|
"Username cannot start with a digit": "Username cannot start with a digit",
|
||||||
|
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
|
||||||
|
"Username must have at least 2 characters": "Username must have at least 2 characters",
|
||||||
|
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
|
||||||
|
"Your region is not allow to signup by phone": "Your region is not allow to signup by phone",
|
||||||
|
"password or code is incorrect": "password or code is incorrect",
|
||||||
|
"password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
|
||||||
|
"unsupported password type: %s": "unsupported password type: %s"
|
||||||
|
},
|
||||||
|
"general": {
|
||||||
|
"Missing parameter": "Missing parameter",
|
||||||
|
"Please login first": "Please login first",
|
||||||
|
"The user: %s doesn't exist": "The user: %s doesn't exist",
|
||||||
|
"don't support captchaProvider: ": "don't support captchaProvider: ",
|
||||||
|
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
|
||||||
|
},
|
||||||
|
"ldap": {
|
||||||
|
"Ldap server exist": "Ldap server exist"
|
||||||
|
},
|
||||||
|
"link": {
|
||||||
|
"Please link first": "Please link first",
|
||||||
|
"This application has no providers": "This application has no providers",
|
||||||
|
"This application has no providers of type": "This application has no providers of type",
|
||||||
|
"This provider can't be unlinked": "This provider can't be unlinked",
|
||||||
|
"You are not the global admin, you can't unlink other users": "You are not the global admin, you can't unlink other users",
|
||||||
|
"You can't unlink yourself, you are not a member of any application": "You can't unlink yourself, you are not a member of any application"
|
||||||
|
},
|
||||||
|
"organization": {
|
||||||
|
"Only admin can modify the %s.": "Only admin can modify the %s.",
|
||||||
|
"The %s is immutable.": "The %s is immutable.",
|
||||||
|
"Unknown modify rule %s.": "Unknown modify rule %s."
|
||||||
|
},
|
||||||
|
"provider": {
|
||||||
|
"Invalid application id": "Invalid application id",
|
||||||
|
"the provider: %s does not exist": "the provider: %s does not exist"
|
||||||
|
},
|
||||||
|
"resource": {
|
||||||
|
"User is nil for tag: avatar": "User is nil for tag: avatar",
|
||||||
|
"Username or fullFilePath is empty: username = %s, fullFilePath = %s": "Username or fullFilePath is empty: username = %s, fullFilePath = %s"
|
||||||
|
},
|
||||||
|
"saml": {
|
||||||
|
"Application %s not found": "Application %s not found"
|
||||||
|
},
|
||||||
|
"saml_sp": {
|
||||||
|
"provider %s's category is not SAML": "provider %s's category is not SAML"
|
||||||
|
},
|
||||||
|
"service": {
|
||||||
|
"Empty parameters for emailForm: %v": "Empty parameters for emailForm: %v",
|
||||||
|
"Invalid Email receivers: %s": "Invalid Email receivers: %s",
|
||||||
|
"Invalid phone receivers: %s": "Invalid phone receivers: %s"
|
||||||
|
},
|
||||||
|
"storage": {
|
||||||
|
"The objectKey: %s is not allowed": "The objectKey: %s is not allowed",
|
||||||
|
"The provider type: %s is not supported": "The provider type: %s is not supported"
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"Empty clientId or clientSecret": "Empty clientId or clientSecret",
|
||||||
|
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application",
|
||||||
|
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret",
|
||||||
|
"Invalid client_id": "Invalid client_id",
|
||||||
|
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list",
|
||||||
|
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"Display name cannot be empty": "Display name cannot be empty",
|
||||||
|
"New password cannot contain blank space.": "New password cannot contain blank space."
|
||||||
|
},
|
||||||
|
"user_upload": {
|
||||||
|
"Failed to import users": "Failed to import users"
|
||||||
|
},
|
||||||
|
"util": {
|
||||||
|
"No application is found for userId: %s": "No application is found for userId: %s",
|
||||||
|
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
||||||
|
"The provider: %s is not found": "The provider: %s is not found"
|
||||||
|
},
|
||||||
|
"verification": {
|
||||||
|
"Code has not been sent yet!": "Code has not been sent yet!",
|
||||||
|
"Invalid captcha provider.": "Invalid captcha provider.",
|
||||||
|
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
|
||||||
|
"Turing test failed.": "Turing test failed.",
|
||||||
|
"Unable to get the email modify rule.": "Unable to get the email modify rule.",
|
||||||
|
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
|
||||||
|
"Unknown type": "Unknown type",
|
||||||
|
"Wrong verification code!": "Wrong verification code!",
|
||||||
|
"You should verify your code in %d min!": "You should verify your code in %d min!",
|
||||||
|
"the user does not exist, please sign up first": "the user does not exist, please sign up first"
|
||||||
|
},
|
||||||
|
"webauthn": {
|
||||||
|
"Found no credentials for this user": "Found no credentials for this user",
|
||||||
|
"Please call WebAuthnSigninBegin first": "Please call WebAuthnSigninBegin first"
|
||||||
|
}
|
||||||
|
}
|
142
i18n/locales/nl/data.json
Normal file
142
i18n/locales/nl/data.json
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
{
|
||||||
|
"account": {
|
||||||
|
"Failed to add user": "Failed to add user",
|
||||||
|
"Get init score failed, error: %w": "Get init score failed, error: %w",
|
||||||
|
"Please sign out first": "Please sign out first",
|
||||||
|
"The application does not allow to sign up new account": "The application does not allow to sign up new account"
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"Challenge method should be S256": "Challenge method should be S256",
|
||||||
|
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s",
|
||||||
|
"Failed to login in: %s": "Failed to login in: %s",
|
||||||
|
"Invalid token": "Invalid token",
|
||||||
|
"State expected: %s, but got: %s": "State expected: %s, but got: %s",
|
||||||
|
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up",
|
||||||
|
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
|
||||||
|
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
|
||||||
|
"The application: %s does not exist": "The application: %s does not exist",
|
||||||
|
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
|
||||||
|
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||||
|
"Unauthorized operation": "Unauthorized operation",
|
||||||
|
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
|
||||||
|
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
|
||||||
|
},
|
||||||
|
"cas": {
|
||||||
|
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||||
|
},
|
||||||
|
"check": {
|
||||||
|
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||||
|
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||||
|
"DisplayName is not valid real name": "DisplayName is not valid real name",
|
||||||
|
"Email already exists": "Email already exists",
|
||||||
|
"Email cannot be empty": "Email cannot be empty",
|
||||||
|
"Email is invalid": "Email is invalid",
|
||||||
|
"Empty username.": "Empty username.",
|
||||||
|
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||||
|
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||||
|
"LastName cannot be blank": "LastName cannot be blank",
|
||||||
|
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||||
|
"Organization does not exist": "Organization does not exist",
|
||||||
|
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||||
|
"Phone already exists": "Phone already exists",
|
||||||
|
"Phone cannot be empty": "Phone cannot be empty",
|
||||||
|
"Phone number is invalid": "Phone number is invalid",
|
||||||
|
"Session outdated, please login again": "Session outdated, please login again",
|
||||||
|
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
|
||||||
|
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||||
|
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.",
|
||||||
|
"Username already exists": "Username already exists",
|
||||||
|
"Username cannot be an email address": "Username cannot be an email address",
|
||||||
|
"Username cannot contain white spaces": "Username cannot contain white spaces",
|
||||||
|
"Username cannot start with a digit": "Username cannot start with a digit",
|
||||||
|
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
|
||||||
|
"Username must have at least 2 characters": "Username must have at least 2 characters",
|
||||||
|
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
|
||||||
|
"Your region is not allow to signup by phone": "Your region is not allow to signup by phone",
|
||||||
|
"password or code is incorrect": "password or code is incorrect",
|
||||||
|
"password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
|
||||||
|
"unsupported password type: %s": "unsupported password type: %s"
|
||||||
|
},
|
||||||
|
"general": {
|
||||||
|
"Missing parameter": "Missing parameter",
|
||||||
|
"Please login first": "Please login first",
|
||||||
|
"The user: %s doesn't exist": "The user: %s doesn't exist",
|
||||||
|
"don't support captchaProvider: ": "don't support captchaProvider: ",
|
||||||
|
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
|
||||||
|
},
|
||||||
|
"ldap": {
|
||||||
|
"Ldap server exist": "Ldap server exist"
|
||||||
|
},
|
||||||
|
"link": {
|
||||||
|
"Please link first": "Please link first",
|
||||||
|
"This application has no providers": "This application has no providers",
|
||||||
|
"This application has no providers of type": "This application has no providers of type",
|
||||||
|
"This provider can't be unlinked": "This provider can't be unlinked",
|
||||||
|
"You are not the global admin, you can't unlink other users": "You are not the global admin, you can't unlink other users",
|
||||||
|
"You can't unlink yourself, you are not a member of any application": "You can't unlink yourself, you are not a member of any application"
|
||||||
|
},
|
||||||
|
"organization": {
|
||||||
|
"Only admin can modify the %s.": "Only admin can modify the %s.",
|
||||||
|
"The %s is immutable.": "The %s is immutable.",
|
||||||
|
"Unknown modify rule %s.": "Unknown modify rule %s."
|
||||||
|
},
|
||||||
|
"provider": {
|
||||||
|
"Invalid application id": "Invalid application id",
|
||||||
|
"the provider: %s does not exist": "the provider: %s does not exist"
|
||||||
|
},
|
||||||
|
"resource": {
|
||||||
|
"User is nil for tag: avatar": "User is nil for tag: avatar",
|
||||||
|
"Username or fullFilePath is empty: username = %s, fullFilePath = %s": "Username or fullFilePath is empty: username = %s, fullFilePath = %s"
|
||||||
|
},
|
||||||
|
"saml": {
|
||||||
|
"Application %s not found": "Application %s not found"
|
||||||
|
},
|
||||||
|
"saml_sp": {
|
||||||
|
"provider %s's category is not SAML": "provider %s's category is not SAML"
|
||||||
|
},
|
||||||
|
"service": {
|
||||||
|
"Empty parameters for emailForm: %v": "Empty parameters for emailForm: %v",
|
||||||
|
"Invalid Email receivers: %s": "Invalid Email receivers: %s",
|
||||||
|
"Invalid phone receivers: %s": "Invalid phone receivers: %s"
|
||||||
|
},
|
||||||
|
"storage": {
|
||||||
|
"The objectKey: %s is not allowed": "The objectKey: %s is not allowed",
|
||||||
|
"The provider type: %s is not supported": "The provider type: %s is not supported"
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"Empty clientId or clientSecret": "Empty clientId or clientSecret",
|
||||||
|
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application",
|
||||||
|
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret",
|
||||||
|
"Invalid client_id": "Invalid client_id",
|
||||||
|
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list",
|
||||||
|
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"Display name cannot be empty": "Display name cannot be empty",
|
||||||
|
"New password cannot contain blank space.": "New password cannot contain blank space."
|
||||||
|
},
|
||||||
|
"user_upload": {
|
||||||
|
"Failed to import users": "Failed to import users"
|
||||||
|
},
|
||||||
|
"util": {
|
||||||
|
"No application is found for userId: %s": "No application is found for userId: %s",
|
||||||
|
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
||||||
|
"The provider: %s is not found": "The provider: %s is not found"
|
||||||
|
},
|
||||||
|
"verification": {
|
||||||
|
"Code has not been sent yet!": "Code has not been sent yet!",
|
||||||
|
"Invalid captcha provider.": "Invalid captcha provider.",
|
||||||
|
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
|
||||||
|
"Turing test failed.": "Turing test failed.",
|
||||||
|
"Unable to get the email modify rule.": "Unable to get the email modify rule.",
|
||||||
|
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
|
||||||
|
"Unknown type": "Unknown type",
|
||||||
|
"Wrong verification code!": "Wrong verification code!",
|
||||||
|
"You should verify your code in %d min!": "You should verify your code in %d min!",
|
||||||
|
"the user does not exist, please sign up first": "the user does not exist, please sign up first"
|
||||||
|
},
|
||||||
|
"webauthn": {
|
||||||
|
"Found no credentials for this user": "Found no credentials for this user",
|
||||||
|
"Please call WebAuthnSigninBegin first": "Please call WebAuthnSigninBegin first"
|
||||||
|
}
|
||||||
|
}
|
142
i18n/locales/pl/data.json
Normal file
142
i18n/locales/pl/data.json
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
{
|
||||||
|
"account": {
|
||||||
|
"Failed to add user": "Failed to add user",
|
||||||
|
"Get init score failed, error: %w": "Get init score failed, error: %w",
|
||||||
|
"Please sign out first": "Please sign out first",
|
||||||
|
"The application does not allow to sign up new account": "The application does not allow to sign up new account"
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"Challenge method should be S256": "Challenge method should be S256",
|
||||||
|
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s",
|
||||||
|
"Failed to login in: %s": "Failed to login in: %s",
|
||||||
|
"Invalid token": "Invalid token",
|
||||||
|
"State expected: %s, but got: %s": "State expected: %s, but got: %s",
|
||||||
|
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up",
|
||||||
|
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
|
||||||
|
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
|
||||||
|
"The application: %s does not exist": "The application: %s does not exist",
|
||||||
|
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
|
||||||
|
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||||
|
"Unauthorized operation": "Unauthorized operation",
|
||||||
|
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
|
||||||
|
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
|
||||||
|
},
|
||||||
|
"cas": {
|
||||||
|
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||||
|
},
|
||||||
|
"check": {
|
||||||
|
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||||
|
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||||
|
"DisplayName is not valid real name": "DisplayName is not valid real name",
|
||||||
|
"Email already exists": "Email already exists",
|
||||||
|
"Email cannot be empty": "Email cannot be empty",
|
||||||
|
"Email is invalid": "Email is invalid",
|
||||||
|
"Empty username.": "Empty username.",
|
||||||
|
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||||
|
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||||
|
"LastName cannot be blank": "LastName cannot be blank",
|
||||||
|
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||||
|
"Organization does not exist": "Organization does not exist",
|
||||||
|
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||||
|
"Phone already exists": "Phone already exists",
|
||||||
|
"Phone cannot be empty": "Phone cannot be empty",
|
||||||
|
"Phone number is invalid": "Phone number is invalid",
|
||||||
|
"Session outdated, please login again": "Session outdated, please login again",
|
||||||
|
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
|
||||||
|
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||||
|
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.",
|
||||||
|
"Username already exists": "Username already exists",
|
||||||
|
"Username cannot be an email address": "Username cannot be an email address",
|
||||||
|
"Username cannot contain white spaces": "Username cannot contain white spaces",
|
||||||
|
"Username cannot start with a digit": "Username cannot start with a digit",
|
||||||
|
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
|
||||||
|
"Username must have at least 2 characters": "Username must have at least 2 characters",
|
||||||
|
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
|
||||||
|
"Your region is not allow to signup by phone": "Your region is not allow to signup by phone",
|
||||||
|
"password or code is incorrect": "password or code is incorrect",
|
||||||
|
"password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
|
||||||
|
"unsupported password type: %s": "unsupported password type: %s"
|
||||||
|
},
|
||||||
|
"general": {
|
||||||
|
"Missing parameter": "Missing parameter",
|
||||||
|
"Please login first": "Please login first",
|
||||||
|
"The user: %s doesn't exist": "The user: %s doesn't exist",
|
||||||
|
"don't support captchaProvider: ": "don't support captchaProvider: ",
|
||||||
|
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
|
||||||
|
},
|
||||||
|
"ldap": {
|
||||||
|
"Ldap server exist": "Ldap server exist"
|
||||||
|
},
|
||||||
|
"link": {
|
||||||
|
"Please link first": "Please link first",
|
||||||
|
"This application has no providers": "This application has no providers",
|
||||||
|
"This application has no providers of type": "This application has no providers of type",
|
||||||
|
"This provider can't be unlinked": "This provider can't be unlinked",
|
||||||
|
"You are not the global admin, you can't unlink other users": "You are not the global admin, you can't unlink other users",
|
||||||
|
"You can't unlink yourself, you are not a member of any application": "You can't unlink yourself, you are not a member of any application"
|
||||||
|
},
|
||||||
|
"organization": {
|
||||||
|
"Only admin can modify the %s.": "Only admin can modify the %s.",
|
||||||
|
"The %s is immutable.": "The %s is immutable.",
|
||||||
|
"Unknown modify rule %s.": "Unknown modify rule %s."
|
||||||
|
},
|
||||||
|
"provider": {
|
||||||
|
"Invalid application id": "Invalid application id",
|
||||||
|
"the provider: %s does not exist": "the provider: %s does not exist"
|
||||||
|
},
|
||||||
|
"resource": {
|
||||||
|
"User is nil for tag: avatar": "User is nil for tag: avatar",
|
||||||
|
"Username or fullFilePath is empty: username = %s, fullFilePath = %s": "Username or fullFilePath is empty: username = %s, fullFilePath = %s"
|
||||||
|
},
|
||||||
|
"saml": {
|
||||||
|
"Application %s not found": "Application %s not found"
|
||||||
|
},
|
||||||
|
"saml_sp": {
|
||||||
|
"provider %s's category is not SAML": "provider %s's category is not SAML"
|
||||||
|
},
|
||||||
|
"service": {
|
||||||
|
"Empty parameters for emailForm: %v": "Empty parameters for emailForm: %v",
|
||||||
|
"Invalid Email receivers: %s": "Invalid Email receivers: %s",
|
||||||
|
"Invalid phone receivers: %s": "Invalid phone receivers: %s"
|
||||||
|
},
|
||||||
|
"storage": {
|
||||||
|
"The objectKey: %s is not allowed": "The objectKey: %s is not allowed",
|
||||||
|
"The provider type: %s is not supported": "The provider type: %s is not supported"
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"Empty clientId or clientSecret": "Empty clientId or clientSecret",
|
||||||
|
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application",
|
||||||
|
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret",
|
||||||
|
"Invalid client_id": "Invalid client_id",
|
||||||
|
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list",
|
||||||
|
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"Display name cannot be empty": "Display name cannot be empty",
|
||||||
|
"New password cannot contain blank space.": "New password cannot contain blank space."
|
||||||
|
},
|
||||||
|
"user_upload": {
|
||||||
|
"Failed to import users": "Failed to import users"
|
||||||
|
},
|
||||||
|
"util": {
|
||||||
|
"No application is found for userId: %s": "No application is found for userId: %s",
|
||||||
|
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
||||||
|
"The provider: %s is not found": "The provider: %s is not found"
|
||||||
|
},
|
||||||
|
"verification": {
|
||||||
|
"Code has not been sent yet!": "Code has not been sent yet!",
|
||||||
|
"Invalid captcha provider.": "Invalid captcha provider.",
|
||||||
|
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
|
||||||
|
"Turing test failed.": "Turing test failed.",
|
||||||
|
"Unable to get the email modify rule.": "Unable to get the email modify rule.",
|
||||||
|
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
|
||||||
|
"Unknown type": "Unknown type",
|
||||||
|
"Wrong verification code!": "Wrong verification code!",
|
||||||
|
"You should verify your code in %d min!": "You should verify your code in %d min!",
|
||||||
|
"the user does not exist, please sign up first": "the user does not exist, please sign up first"
|
||||||
|
},
|
||||||
|
"webauthn": {
|
||||||
|
"Found no credentials for this user": "Found no credentials for this user",
|
||||||
|
"Please call WebAuthnSigninBegin first": "Please call WebAuthnSigninBegin first"
|
||||||
|
}
|
||||||
|
}
|
142
i18n/locales/sv/data.json
Normal file
142
i18n/locales/sv/data.json
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
{
|
||||||
|
"account": {
|
||||||
|
"Failed to add user": "Failed to add user",
|
||||||
|
"Get init score failed, error: %w": "Get init score failed, error: %w",
|
||||||
|
"Please sign out first": "Please sign out first",
|
||||||
|
"The application does not allow to sign up new account": "The application does not allow to sign up new account"
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"Challenge method should be S256": "Challenge method should be S256",
|
||||||
|
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s",
|
||||||
|
"Failed to login in: %s": "Failed to login in: %s",
|
||||||
|
"Invalid token": "Invalid token",
|
||||||
|
"State expected: %s, but got: %s": "State expected: %s, but got: %s",
|
||||||
|
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up",
|
||||||
|
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
|
||||||
|
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
|
||||||
|
"The application: %s does not exist": "The application: %s does not exist",
|
||||||
|
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
|
||||||
|
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||||
|
"Unauthorized operation": "Unauthorized operation",
|
||||||
|
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
|
||||||
|
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
|
||||||
|
},
|
||||||
|
"cas": {
|
||||||
|
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||||
|
},
|
||||||
|
"check": {
|
||||||
|
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||||
|
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||||
|
"DisplayName is not valid real name": "DisplayName is not valid real name",
|
||||||
|
"Email already exists": "Email already exists",
|
||||||
|
"Email cannot be empty": "Email cannot be empty",
|
||||||
|
"Email is invalid": "Email is invalid",
|
||||||
|
"Empty username.": "Empty username.",
|
||||||
|
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||||
|
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||||
|
"LastName cannot be blank": "LastName cannot be blank",
|
||||||
|
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||||
|
"Organization does not exist": "Organization does not exist",
|
||||||
|
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||||
|
"Phone already exists": "Phone already exists",
|
||||||
|
"Phone cannot be empty": "Phone cannot be empty",
|
||||||
|
"Phone number is invalid": "Phone number is invalid",
|
||||||
|
"Session outdated, please login again": "Session outdated, please login again",
|
||||||
|
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
|
||||||
|
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||||
|
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.",
|
||||||
|
"Username already exists": "Username already exists",
|
||||||
|
"Username cannot be an email address": "Username cannot be an email address",
|
||||||
|
"Username cannot contain white spaces": "Username cannot contain white spaces",
|
||||||
|
"Username cannot start with a digit": "Username cannot start with a digit",
|
||||||
|
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
|
||||||
|
"Username must have at least 2 characters": "Username must have at least 2 characters",
|
||||||
|
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
|
||||||
|
"Your region is not allow to signup by phone": "Your region is not allow to signup by phone",
|
||||||
|
"password or code is incorrect": "password or code is incorrect",
|
||||||
|
"password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
|
||||||
|
"unsupported password type: %s": "unsupported password type: %s"
|
||||||
|
},
|
||||||
|
"general": {
|
||||||
|
"Missing parameter": "Missing parameter",
|
||||||
|
"Please login first": "Please login first",
|
||||||
|
"The user: %s doesn't exist": "The user: %s doesn't exist",
|
||||||
|
"don't support captchaProvider: ": "don't support captchaProvider: ",
|
||||||
|
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
|
||||||
|
},
|
||||||
|
"ldap": {
|
||||||
|
"Ldap server exist": "Ldap server exist"
|
||||||
|
},
|
||||||
|
"link": {
|
||||||
|
"Please link first": "Please link first",
|
||||||
|
"This application has no providers": "This application has no providers",
|
||||||
|
"This application has no providers of type": "This application has no providers of type",
|
||||||
|
"This provider can't be unlinked": "This provider can't be unlinked",
|
||||||
|
"You are not the global admin, you can't unlink other users": "You are not the global admin, you can't unlink other users",
|
||||||
|
"You can't unlink yourself, you are not a member of any application": "You can't unlink yourself, you are not a member of any application"
|
||||||
|
},
|
||||||
|
"organization": {
|
||||||
|
"Only admin can modify the %s.": "Only admin can modify the %s.",
|
||||||
|
"The %s is immutable.": "The %s is immutable.",
|
||||||
|
"Unknown modify rule %s.": "Unknown modify rule %s."
|
||||||
|
},
|
||||||
|
"provider": {
|
||||||
|
"Invalid application id": "Invalid application id",
|
||||||
|
"the provider: %s does not exist": "the provider: %s does not exist"
|
||||||
|
},
|
||||||
|
"resource": {
|
||||||
|
"User is nil for tag: avatar": "User is nil for tag: avatar",
|
||||||
|
"Username or fullFilePath is empty: username = %s, fullFilePath = %s": "Username or fullFilePath is empty: username = %s, fullFilePath = %s"
|
||||||
|
},
|
||||||
|
"saml": {
|
||||||
|
"Application %s not found": "Application %s not found"
|
||||||
|
},
|
||||||
|
"saml_sp": {
|
||||||
|
"provider %s's category is not SAML": "provider %s's category is not SAML"
|
||||||
|
},
|
||||||
|
"service": {
|
||||||
|
"Empty parameters for emailForm: %v": "Empty parameters for emailForm: %v",
|
||||||
|
"Invalid Email receivers: %s": "Invalid Email receivers: %s",
|
||||||
|
"Invalid phone receivers: %s": "Invalid phone receivers: %s"
|
||||||
|
},
|
||||||
|
"storage": {
|
||||||
|
"The objectKey: %s is not allowed": "The objectKey: %s is not allowed",
|
||||||
|
"The provider type: %s is not supported": "The provider type: %s is not supported"
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"Empty clientId or clientSecret": "Empty clientId or clientSecret",
|
||||||
|
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application",
|
||||||
|
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret",
|
||||||
|
"Invalid client_id": "Invalid client_id",
|
||||||
|
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list",
|
||||||
|
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"Display name cannot be empty": "Display name cannot be empty",
|
||||||
|
"New password cannot contain blank space.": "New password cannot contain blank space."
|
||||||
|
},
|
||||||
|
"user_upload": {
|
||||||
|
"Failed to import users": "Failed to import users"
|
||||||
|
},
|
||||||
|
"util": {
|
||||||
|
"No application is found for userId: %s": "No application is found for userId: %s",
|
||||||
|
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
||||||
|
"The provider: %s is not found": "The provider: %s is not found"
|
||||||
|
},
|
||||||
|
"verification": {
|
||||||
|
"Code has not been sent yet!": "Code has not been sent yet!",
|
||||||
|
"Invalid captcha provider.": "Invalid captcha provider.",
|
||||||
|
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
|
||||||
|
"Turing test failed.": "Turing test failed.",
|
||||||
|
"Unable to get the email modify rule.": "Unable to get the email modify rule.",
|
||||||
|
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
|
||||||
|
"Unknown type": "Unknown type",
|
||||||
|
"Wrong verification code!": "Wrong verification code!",
|
||||||
|
"You should verify your code in %d min!": "You should verify your code in %d min!",
|
||||||
|
"the user does not exist, please sign up first": "the user does not exist, please sign up first"
|
||||||
|
},
|
||||||
|
"webauthn": {
|
||||||
|
"Found no credentials for this user": "Found no credentials for this user",
|
||||||
|
"Please call WebAuthnSigninBegin first": "Please call WebAuthnSigninBegin first"
|
||||||
|
}
|
||||||
|
}
|
142
i18n/locales/uk/data.json
Normal file
142
i18n/locales/uk/data.json
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
{
|
||||||
|
"account": {
|
||||||
|
"Failed to add user": "Failed to add user",
|
||||||
|
"Get init score failed, error: %w": "Get init score failed, error: %w",
|
||||||
|
"Please sign out first": "Please sign out first",
|
||||||
|
"The application does not allow to sign up new account": "The application does not allow to sign up new account"
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"Challenge method should be S256": "Challenge method should be S256",
|
||||||
|
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s",
|
||||||
|
"Failed to login in: %s": "Failed to login in: %s",
|
||||||
|
"Invalid token": "Invalid token",
|
||||||
|
"State expected: %s, but got: %s": "State expected: %s, but got: %s",
|
||||||
|
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up",
|
||||||
|
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
|
||||||
|
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
|
||||||
|
"The application: %s does not exist": "The application: %s does not exist",
|
||||||
|
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
|
||||||
|
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||||
|
"Unauthorized operation": "Unauthorized operation",
|
||||||
|
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
|
||||||
|
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
|
||||||
|
},
|
||||||
|
"cas": {
|
||||||
|
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||||
|
},
|
||||||
|
"check": {
|
||||||
|
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||||
|
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||||
|
"DisplayName is not valid real name": "DisplayName is not valid real name",
|
||||||
|
"Email already exists": "Email already exists",
|
||||||
|
"Email cannot be empty": "Email cannot be empty",
|
||||||
|
"Email is invalid": "Email is invalid",
|
||||||
|
"Empty username.": "Empty username.",
|
||||||
|
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||||
|
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||||
|
"LastName cannot be blank": "LastName cannot be blank",
|
||||||
|
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||||
|
"Organization does not exist": "Organization does not exist",
|
||||||
|
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||||
|
"Phone already exists": "Phone already exists",
|
||||||
|
"Phone cannot be empty": "Phone cannot be empty",
|
||||||
|
"Phone number is invalid": "Phone number is invalid",
|
||||||
|
"Session outdated, please login again": "Session outdated, please login again",
|
||||||
|
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
|
||||||
|
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||||
|
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.",
|
||||||
|
"Username already exists": "Username already exists",
|
||||||
|
"Username cannot be an email address": "Username cannot be an email address",
|
||||||
|
"Username cannot contain white spaces": "Username cannot contain white spaces",
|
||||||
|
"Username cannot start with a digit": "Username cannot start with a digit",
|
||||||
|
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
|
||||||
|
"Username must have at least 2 characters": "Username must have at least 2 characters",
|
||||||
|
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
|
||||||
|
"Your region is not allow to signup by phone": "Your region is not allow to signup by phone",
|
||||||
|
"password or code is incorrect": "password or code is incorrect",
|
||||||
|
"password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
|
||||||
|
"unsupported password type: %s": "unsupported password type: %s"
|
||||||
|
},
|
||||||
|
"general": {
|
||||||
|
"Missing parameter": "Missing parameter",
|
||||||
|
"Please login first": "Please login first",
|
||||||
|
"The user: %s doesn't exist": "The user: %s doesn't exist",
|
||||||
|
"don't support captchaProvider: ": "don't support captchaProvider: ",
|
||||||
|
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
|
||||||
|
},
|
||||||
|
"ldap": {
|
||||||
|
"Ldap server exist": "Ldap server exist"
|
||||||
|
},
|
||||||
|
"link": {
|
||||||
|
"Please link first": "Please link first",
|
||||||
|
"This application has no providers": "This application has no providers",
|
||||||
|
"This application has no providers of type": "This application has no providers of type",
|
||||||
|
"This provider can't be unlinked": "This provider can't be unlinked",
|
||||||
|
"You are not the global admin, you can't unlink other users": "You are not the global admin, you can't unlink other users",
|
||||||
|
"You can't unlink yourself, you are not a member of any application": "You can't unlink yourself, you are not a member of any application"
|
||||||
|
},
|
||||||
|
"organization": {
|
||||||
|
"Only admin can modify the %s.": "Only admin can modify the %s.",
|
||||||
|
"The %s is immutable.": "The %s is immutable.",
|
||||||
|
"Unknown modify rule %s.": "Unknown modify rule %s."
|
||||||
|
},
|
||||||
|
"provider": {
|
||||||
|
"Invalid application id": "Invalid application id",
|
||||||
|
"the provider: %s does not exist": "the provider: %s does not exist"
|
||||||
|
},
|
||||||
|
"resource": {
|
||||||
|
"User is nil for tag: avatar": "User is nil for tag: avatar",
|
||||||
|
"Username or fullFilePath is empty: username = %s, fullFilePath = %s": "Username or fullFilePath is empty: username = %s, fullFilePath = %s"
|
||||||
|
},
|
||||||
|
"saml": {
|
||||||
|
"Application %s not found": "Application %s not found"
|
||||||
|
},
|
||||||
|
"saml_sp": {
|
||||||
|
"provider %s's category is not SAML": "provider %s's category is not SAML"
|
||||||
|
},
|
||||||
|
"service": {
|
||||||
|
"Empty parameters for emailForm: %v": "Empty parameters for emailForm: %v",
|
||||||
|
"Invalid Email receivers: %s": "Invalid Email receivers: %s",
|
||||||
|
"Invalid phone receivers: %s": "Invalid phone receivers: %s"
|
||||||
|
},
|
||||||
|
"storage": {
|
||||||
|
"The objectKey: %s is not allowed": "The objectKey: %s is not allowed",
|
||||||
|
"The provider type: %s is not supported": "The provider type: %s is not supported"
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"Empty clientId or clientSecret": "Empty clientId or clientSecret",
|
||||||
|
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application",
|
||||||
|
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret",
|
||||||
|
"Invalid client_id": "Invalid client_id",
|
||||||
|
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list",
|
||||||
|
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"Display name cannot be empty": "Display name cannot be empty",
|
||||||
|
"New password cannot contain blank space.": "New password cannot contain blank space."
|
||||||
|
},
|
||||||
|
"user_upload": {
|
||||||
|
"Failed to import users": "Failed to import users"
|
||||||
|
},
|
||||||
|
"util": {
|
||||||
|
"No application is found for userId: %s": "No application is found for userId: %s",
|
||||||
|
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
||||||
|
"The provider: %s is not found": "The provider: %s is not found"
|
||||||
|
},
|
||||||
|
"verification": {
|
||||||
|
"Code has not been sent yet!": "Code has not been sent yet!",
|
||||||
|
"Invalid captcha provider.": "Invalid captcha provider.",
|
||||||
|
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
|
||||||
|
"Turing test failed.": "Turing test failed.",
|
||||||
|
"Unable to get the email modify rule.": "Unable to get the email modify rule.",
|
||||||
|
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
|
||||||
|
"Unknown type": "Unknown type",
|
||||||
|
"Wrong verification code!": "Wrong verification code!",
|
||||||
|
"You should verify your code in %d min!": "You should verify your code in %d min!",
|
||||||
|
"the user does not exist, please sign up first": "the user does not exist, please sign up first"
|
||||||
|
},
|
||||||
|
"webauthn": {
|
||||||
|
"Found no credentials for this user": "Found no credentials for this user",
|
||||||
|
"Please call WebAuthnSigninBegin first": "Please call WebAuthnSigninBegin first"
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
@ -9,11 +9,11 @@
|
|||||||
"passwordType": "plain",
|
"passwordType": "plain",
|
||||||
"passwordSalt": "",
|
"passwordSalt": "",
|
||||||
"passwordOptions": ["AtLeast6"],
|
"passwordOptions": ["AtLeast6"],
|
||||||
"countryCodes": ["US", "ES", "CN", "FR", "DE", "GB", "JP", "KR", "VN", "ID", "SG", "IN", "IT", "MY", "TR", "DZ", "IL", "PH"],
|
"countryCodes": ["US", "GB", "ES", "FR", "DE", "CN", "JP", "KR", "VN", "ID", "SG", "IN", "IT", "MY", "TR", "DZ", "IL", "PH", "NL", "PL", "FI", "SE", "UA", "KZ"],
|
||||||
"defaultAvatar": "",
|
"defaultAvatar": "",
|
||||||
"defaultApplication": "",
|
"defaultApplication": "",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"languages": ["en", "zh", "es", "fr", "de", "id", "ja", "ko", "ru", "vi", "it", "ms", "tr","ar", "he", "fi"],
|
"languages": ["en", "zh", "es", "fr", "de", "id", "ja", "ko", "ru", "vi", "it", "ms", "tr","ar", "he", "nl", "pl", "fi", "sv", "uk", "kk", "fa"],
|
||||||
"masterPassword": "",
|
"masterPassword": "",
|
||||||
"initScore": 2000,
|
"initScore": 2000,
|
||||||
"enableSoftDeletion": false,
|
"enableSoftDeletion": false,
|
||||||
|
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
|
||||||
|
}
|
@ -151,7 +151,7 @@ func (adapter *Adapter) InitAdapter() error {
|
|||||||
if adapter.Adapter == nil {
|
if adapter.Adapter == nil {
|
||||||
var dataSourceName string
|
var dataSourceName string
|
||||||
|
|
||||||
if adapter.builtInAdapter() {
|
if adapter.isBuiltIn() {
|
||||||
dataSourceName = conf.GetConfigString("dataSourceName")
|
dataSourceName = conf.GetConfigString("dataSourceName")
|
||||||
if adapter.DatabaseType == "mysql" {
|
if adapter.DatabaseType == "mysql" {
|
||||||
dataSourceName = dataSourceName + adapter.Database
|
dataSourceName = dataSourceName + adapter.Database
|
||||||
@ -183,6 +183,14 @@ func (adapter *Adapter) InitAdapter() error {
|
|||||||
|
|
||||||
var err error
|
var err error
|
||||||
engine, err := xorm.NewEngine(adapter.DatabaseType, dataSourceName)
|
engine, err := xorm.NewEngine(adapter.DatabaseType, dataSourceName)
|
||||||
|
|
||||||
|
if adapter.isBuiltIn() && adapter.DatabaseType == "postgres" {
|
||||||
|
schema := util.GetValueFromDataSourceName("search_path", dataSourceName)
|
||||||
|
if schema != "" {
|
||||||
|
engine.SetSchema(schema)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
adapter.Adapter, err = xormadapter.NewAdapterByEngineWithTableName(engine, adapter.getTable(), adapter.TableNamePrefix)
|
adapter.Adapter, err = xormadapter.NewAdapterByEngineWithTableName(engine, adapter.getTable(), adapter.TableNamePrefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -211,7 +219,7 @@ func adapterChangeTrigger(oldName string, newName string) error {
|
|||||||
return session.Commit()
|
return session.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (adapter *Adapter) builtInAdapter() bool {
|
func (adapter *Adapter) isBuiltIn() bool {
|
||||||
if adapter.Owner != "built-in" {
|
if adapter.Owner != "built-in" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -57,6 +57,7 @@ type Application struct {
|
|||||||
SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"`
|
SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"`
|
||||||
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
|
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
|
||||||
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
|
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
|
||||||
|
CertPublicKey string `xorm:"-" json:"certPublicKey"`
|
||||||
Tags []string `xorm:"mediumtext" json:"tags"`
|
Tags []string `xorm:"mediumtext" json:"tags"`
|
||||||
InvitationCodes []string `xorm:"varchar(200)" json:"invitationCodes"`
|
InvitationCodes []string `xorm:"varchar(200)" json:"invitationCodes"`
|
||||||
|
|
||||||
@ -427,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 {
|
||||||
@ -406,10 +414,6 @@ func CheckUsername(username string, lang string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func CheckUpdateUser(oldUser, user *User, lang string) string {
|
func CheckUpdateUser(oldUser, user *User, lang string) string {
|
||||||
if user.DisplayName == "" {
|
|
||||||
return i18n.Translate(lang, "user:Display name cannot be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
if oldUser.Name != user.Name {
|
if oldUser.Name != user.Name {
|
||||||
if msg := CheckUsername(user.Name, lang); msg != "" {
|
if msg := CheckUsername(user.Name, lang); msg != "" {
|
||||||
return msg
|
return msg
|
||||||
@ -419,7 +423,7 @@ func CheckUpdateUser(oldUser, user *User, lang string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Email != user.Email {
|
if oldUser.Email != user.Email {
|
||||||
if HasUserByField(user.Name, "email", user.Email) {
|
if HasUserByField(user.Owner, "email", user.Email) {
|
||||||
return i18n.Translate(lang, "check:Email already exists")
|
return i18n.Translate(lang, "check:Email already exists")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -18,11 +18,14 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/beego/beego"
|
"github.com/beego/beego"
|
||||||
"github.com/casdoor/casdoor/conf"
|
"github.com/casdoor/casdoor/conf"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
xormadapter "github.com/casdoor/xorm-adapter/v3"
|
xormadapter "github.com/casdoor/xorm-adapter/v3"
|
||||||
_ "github.com/denisenkom/go-mssqldb" // db = mssql
|
_ "github.com/denisenkom/go-mssqldb" // db = mssql
|
||||||
_ "github.com/go-sql-driver/mysql" // db = mysql
|
_ "github.com/go-sql-driver/mysql" // db = mysql
|
||||||
@ -65,6 +68,17 @@ func InitConfig() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func InitAdapter() {
|
func InitAdapter() {
|
||||||
|
if conf.GetConfigString("driverName") == "" {
|
||||||
|
if !util.FileExist("conf/app.conf") {
|
||||||
|
dir, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
dir = strings.ReplaceAll(dir, "\\", "/")
|
||||||
|
panic(fmt.Sprintf("The Casdoor config file: \"app.conf\" was not found, it should be placed at: \"%s/conf/app.conf\"", dir))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if createDatabase {
|
if createDatabase {
|
||||||
err := createDatabaseForPostgres(conf.GetConfigString("driverName"), conf.GetConfigDataSourceName(), conf.GetConfigString("dbName"))
|
err := createDatabaseForPostgres(conf.GetConfigString("driverName"), conf.GetConfigDataSourceName(), conf.GetConfigString("dbName"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -122,9 +136,14 @@ func NewAdapter(driverName string, dataSourceName string, dbName string) *Ormer
|
|||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func refineDataSourceNameForPostgres(dataSourceName string) string {
|
||||||
|
reg := regexp.MustCompile(`dbname=[^ ]+\s*`)
|
||||||
|
return reg.ReplaceAllString(dataSourceName, "")
|
||||||
|
}
|
||||||
|
|
||||||
func createDatabaseForPostgres(driverName string, dataSourceName string, dbName string) error {
|
func createDatabaseForPostgres(driverName string, dataSourceName string, dbName string) error {
|
||||||
if driverName == "postgres" {
|
if driverName == "postgres" {
|
||||||
db, err := sql.Open(driverName, dataSourceName)
|
db, err := sql.Open(driverName, refineDataSourceNameForPostgres(dataSourceName))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -136,6 +155,21 @@ func createDatabaseForPostgres(driverName string, dataSourceName string, dbName
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
schema := util.GetValueFromDataSourceName("search_path", dataSourceName)
|
||||||
|
if schema != "" {
|
||||||
|
db, err = sql.Open(driverName, dataSourceName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
_, err = db.Exec(fmt.Sprintf("CREATE SCHEMA %s;", schema))
|
||||||
|
if err != nil {
|
||||||
|
if !strings.Contains(err.Error(), "already exists") {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
} else {
|
} else {
|
||||||
@ -168,6 +202,12 @@ func (a *Ormer) open() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
if a.driverName == "postgres" {
|
||||||
|
schema := util.GetValueFromDataSourceName("search_path", dataSourceName)
|
||||||
|
if schema != "" {
|
||||||
|
engine.SetSchema(schema)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
a.Engine = engine
|
a.Engine = engine
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
@ -79,9 +79,7 @@ func (p *Permission) setEnforcerAdapter(enforcer *casbin.Enforcer) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
|
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
|
||||||
driverName := conf.GetConfigString("driverName")
|
adapter, err := xormadapter.NewAdapterByEngineWithTableName(ormer.Engine, tableName, tableNamePrefix)
|
||||||
dataSourceName := conf.GetConfigRealDataSourceName(driverName)
|
|
||||||
adapter, err := xormadapter.NewAdapterWithTableName(driverName, dataSourceName, tableName, tableNamePrefix, true)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ package object
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"github.com/xorm-io/core"
|
"github.com/xorm-io/core"
|
||||||
@ -28,10 +29,10 @@ type Plan struct {
|
|||||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||||
Description string `xorm:"varchar(100)" json:"description"`
|
Description string `xorm:"varchar(100)" json:"description"`
|
||||||
|
|
||||||
PricePerMonth float64 `json:"pricePerMonth"`
|
Price float64 `json:"price"`
|
||||||
PricePerYear float64 `json:"pricePerYear"`
|
|
||||||
Currency string `xorm:"varchar(100)" json:"currency"`
|
Currency string `xorm:"varchar(100)" json:"currency"`
|
||||||
Product string `json:"product"` // related product id
|
Period string `xorm:"varchar(100)" json:"period"`
|
||||||
|
Product string `xorm:"varchar(100)" json:"product"`
|
||||||
PaymentProviders []string `xorm:"varchar(100)" json:"paymentProviders"` // payment providers for related product
|
PaymentProviders []string `xorm:"varchar(100)" json:"paymentProviders"` // payment providers for related product
|
||||||
IsEnabled bool `json:"isEnabled"`
|
IsEnabled bool `json:"isEnabled"`
|
||||||
|
|
||||||
@ -39,10 +40,28 @@ type Plan struct {
|
|||||||
Options []string `xorm:"-" json:"options"`
|
Options []string `xorm:"-" json:"options"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
PeriodMonthly = "Monthly"
|
||||||
|
PeriodYearly = "Yearly"
|
||||||
|
)
|
||||||
|
|
||||||
func (plan *Plan) GetId() string {
|
func (plan *Plan) GetId() string {
|
||||||
return fmt.Sprintf("%s/%s", plan.Owner, plan.Name)
|
return fmt.Sprintf("%s/%s", plan.Owner, plan.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetDuration(period string) (startTime time.Time, endTime time.Time) {
|
||||||
|
if period == PeriodYearly {
|
||||||
|
startTime = time.Now()
|
||||||
|
endTime = startTime.AddDate(1, 0, 0)
|
||||||
|
} else if period == PeriodMonthly {
|
||||||
|
startTime = time.Now()
|
||||||
|
endTime = startTime.AddDate(0, 1, 0)
|
||||||
|
} else {
|
||||||
|
panic(fmt.Sprintf("invalid period: %s", period))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func GetPlanCount(owner, field, value string) (int64, error) {
|
func GetPlanCount(owner, field, value string) (int64, error) {
|
||||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||||
return session.Count(&Plan{})
|
return session.Count(&Plan{})
|
||||||
|
@ -32,12 +32,6 @@ type Pricing struct {
|
|||||||
IsEnabled bool `json:"isEnabled"`
|
IsEnabled bool `json:"isEnabled"`
|
||||||
TrialDuration int `json:"trialDuration"`
|
TrialDuration int `json:"trialDuration"`
|
||||||
Application string `xorm:"varchar(100)" json:"application"`
|
Application string `xorm:"varchar(100)" json:"application"`
|
||||||
|
|
||||||
Submitter string `xorm:"varchar(100)" json:"submitter"`
|
|
||||||
Approver string `xorm:"varchar(100)" json:"approver"`
|
|
||||||
ApproveTime string `xorm:"varchar(100)" json:"approveTime"`
|
|
||||||
|
|
||||||
State string `xorm:"varchar(100)" json:"state"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pricing *Pricing) GetId() string {
|
func (pricing *Pricing) GetId() string {
|
||||||
|
@ -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
|
||||||
@ -190,10 +189,17 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
|
|||||||
if user.Type == "paid-user" {
|
if user.Type == "paid-user" {
|
||||||
// Create a subscription for `paid-user`
|
// Create a subscription for `paid-user`
|
||||||
if pricingName != "" && planName != "" {
|
if pricingName != "" && planName != "" {
|
||||||
sub := NewSubscription(owner, user.Name, pricingName, planName, paymentName)
|
plan, err := GetPlan(util.GetId(owner, planName))
|
||||||
_, err := AddSubscription(sub)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return nil, err
|
||||||
|
}
|
||||||
|
if plan == nil {
|
||||||
|
return nil, fmt.Errorf("the plan: %s does not exist", planName)
|
||||||
|
}
|
||||||
|
sub := NewSubscription(owner, user.Name, plan.Name, paymentName, plan.Period)
|
||||||
|
_, err = AddSubscription(sub)
|
||||||
|
if err != nil {
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
@ -201,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(),
|
||||||
@ -223,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,
|
||||||
}
|
}
|
||||||
@ -231,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 {
|
||||||
@ -267,14 +274,14 @@ func CreateProductForPlan(plan *Plan) *Product {
|
|||||||
product := &Product{
|
product := &Product{
|
||||||
Owner: plan.Owner,
|
Owner: plan.Owner,
|
||||||
Name: fmt.Sprintf("product_%v", util.GetRandomName()),
|
Name: fmt.Sprintf("product_%v", util.GetRandomName()),
|
||||||
DisplayName: fmt.Sprintf("Auto Created Product for Plan %v(%v)", plan.GetId(), plan.DisplayName),
|
DisplayName: fmt.Sprintf("Product for Plan %v/%v/%v", plan.Name, plan.DisplayName, plan.Period),
|
||||||
CreatedTime: plan.CreatedTime,
|
CreatedTime: plan.CreatedTime,
|
||||||
|
|
||||||
Image: "https://cdn.casbin.org/img/casdoor-logo_1185x256.png", // TODO
|
Image: "https://cdn.casbin.org/img/casdoor-logo_1185x256.png", // TODO
|
||||||
Detail: fmt.Sprintf("This Product was auto created for Plan %v(%v)", plan.GetId(), plan.DisplayName),
|
Detail: fmt.Sprintf("This product was auto created for plan %v(%v), subscription period is %v", plan.Name, plan.DisplayName, plan.Period),
|
||||||
Description: plan.Description,
|
Description: plan.Description,
|
||||||
Tag: "auto_created_product_for_plan",
|
Tag: "auto_created_product_for_plan",
|
||||||
Price: plan.PricePerMonth, // TODO
|
Price: plan.Price,
|
||||||
Currency: plan.Currency,
|
Currency: plan.Currency,
|
||||||
|
|
||||||
Quantity: 999,
|
Quantity: 999,
|
||||||
@ -290,9 +297,10 @@ func CreateProductForPlan(plan *Plan) *Product {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func UpdateProductForPlan(plan *Plan, product *Product) {
|
func UpdateProductForPlan(plan *Plan, product *Product) {
|
||||||
product.DisplayName = fmt.Sprintf("Auto Created Product for Plan %v(%v)", plan.GetId(), plan.DisplayName)
|
product.Owner = plan.Owner
|
||||||
product.Detail = fmt.Sprintf("This Product was auto created for Plan %v(%v)", plan.GetId(), plan.DisplayName)
|
product.DisplayName = fmt.Sprintf("Product for Plan %v/%v/%v", plan.Name, plan.DisplayName, plan.Period)
|
||||||
product.Price = plan.PricePerMonth // TODO
|
product.Detail = fmt.Sprintf("This product was auto created for plan %v(%v), subscription period is %v", plan.Name, plan.DisplayName, plan.Period)
|
||||||
product.Providers = plan.PaymentProviders
|
product.Price = plan.Price
|
||||||
product.Currency = plan.Currency
|
product.Currency = plan.Currency
|
||||||
|
product.Providers = plan.PaymentProviders
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -50,7 +50,7 @@ type Subscription struct {
|
|||||||
|
|
||||||
StartTime time.Time `json:"startTime"`
|
StartTime time.Time `json:"startTime"`
|
||||||
EndTime time.Time `json:"endTime"`
|
EndTime time.Time `json:"endTime"`
|
||||||
Duration int `json:"duration"`
|
Period string `xorm:"varchar(100)" json:"period"`
|
||||||
State SubscriptionState `xorm:"varchar(100)" json:"state"`
|
State SubscriptionState `xorm:"varchar(100)" json:"state"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,7 +103,8 @@ func (sub *Subscription) UpdateState() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSubscription(owner, userName, pricingName, planName, paymentName string) *Subscription {
|
func NewSubscription(owner, userName, planName, paymentName, period string) *Subscription {
|
||||||
|
startTime, endTime := GetDuration(period)
|
||||||
id := util.GenerateId()[:6]
|
id := util.GenerateId()[:6]
|
||||||
return &Subscription{
|
return &Subscription{
|
||||||
Owner: owner,
|
Owner: owner,
|
||||||
@ -112,13 +113,12 @@ func NewSubscription(owner, userName, pricingName, planName, paymentName string)
|
|||||||
CreatedTime: util.GetCurrentTime(),
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
|
||||||
User: userName,
|
User: userName,
|
||||||
Pricing: pricingName,
|
|
||||||
Plan: planName,
|
Plan: planName,
|
||||||
Payment: paymentName,
|
Payment: paymentName,
|
||||||
|
|
||||||
StartTime: time.Now(),
|
StartTime: startTime,
|
||||||
EndTime: time.Now().AddDate(0, 0, 30),
|
EndTime: endTime,
|
||||||
Duration: 30, // TODO
|
Period: period,
|
||||||
State: SubStatePending, // waiting for payment complete
|
State: SubStatePending, // waiting for payment complete
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -148,6 +148,6 @@ func (syncer *Syncer) syncUsers() error {
|
|||||||
func (syncer *Syncer) syncUsersNoError() {
|
func (syncer *Syncer) syncUsersNoError() {
|
||||||
err := syncer.syncUsers()
|
err := syncer.syncUsers()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
fmt.Printf("syncUsersNoError() error: %s\n", err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,52 @@ 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 {
|
||||||
|
if v != "" {
|
||||||
|
authenticationSuccess.Attributes.UserAttributes.Attributes = append(authenticationSuccess.Attributes.UserAttributes.Attributes, &CasNamedAttribute{
|
||||||
|
Name: k,
|
||||||
|
Value: fmt.Sprintf("%v", v),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
@ -627,12 +628,10 @@ func AddUser(user *User) (bool, error) {
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.PasswordType == "" && organization.PasswordType != "" {
|
if user.PasswordType == "" || user.PasswordType == "plain" {
|
||||||
user.PasswordType = organization.PasswordType
|
user.UpdateUserPassword(organization)
|
||||||
}
|
}
|
||||||
|
|
||||||
user.UpdateUserPassword(organization)
|
|
||||||
|
|
||||||
err = user.UpdateUserHash()
|
err = user.UpdateUserHash()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@ -759,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
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
10
pp/dummy.go
10
pp/dummy.go
@ -14,11 +14,6 @@
|
|||||||
|
|
||||||
package pp
|
package pp
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DummyPaymentProvider struct{}
|
type DummyPaymentProvider struct{}
|
||||||
|
|
||||||
func NewDummyPaymentProvider() (*DummyPaymentProvider, error) {
|
func NewDummyPaymentProvider() (*DummyPaymentProvider, error) {
|
||||||
@ -27,11 +22,10 @@ func NewDummyPaymentProvider() (*DummyPaymentProvider, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pp *DummyPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) {
|
func (pp *DummyPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) {
|
||||||
payUrl := fmt.Sprintf("/payments/%s/result", paymentName)
|
return returnUrl, "", nil
|
||||||
return payUrl, "", 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,10 +14,6 @@
|
|||||||
|
|
||||||
package pp
|
package pp
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PaymentState string
|
type PaymentState string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -44,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")
|
||||||
@ -273,7 +273,7 @@ func initAPI() {
|
|||||||
beego.Router("/cas/:organization/:application/proxy", &controllers.RootController{}, "GET:CasProxy")
|
beego.Router("/cas/:organization/:application/proxy", &controllers.RootController{}, "GET:CasProxy")
|
||||||
beego.Router("/cas/:organization/:application/validate", &controllers.RootController{}, "GET:CasValidate")
|
beego.Router("/cas/:organization/:application/validate", &controllers.RootController{}, "GET:CasValidate")
|
||||||
|
|
||||||
beego.Router("/cas/:organization/:application/p3/serviceValidate", &controllers.RootController{}, "GET:CasP3ServiceAndProxyValidate")
|
beego.Router("/cas/:organization/:application/p3/serviceValidate", &controllers.RootController{}, "GET:CasP3ServiceValidate")
|
||||||
beego.Router("/cas/:organization/:application/p3/proxyValidate", &controllers.RootController{}, "GET:CasP3ServiceAndProxyValidate")
|
beego.Router("/cas/:organization/:application/p3/proxyValidate", &controllers.RootController{}, "GET:CasP3ProxyValidate")
|
||||||
beego.Router("/cas/:organization/:application/samlValidate", &controllers.RootController{}, "POST:SamlValidate")
|
beego.Router("/cas/:organization/:application/samlValidate", &controllers.RootController{}, "POST:SamlValidate")
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ package routers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@ -59,6 +60,13 @@ func StaticFilter(ctx *context.Context) {
|
|||||||
path = "web/build/index.html"
|
path = "web/build/index.html"
|
||||||
}
|
}
|
||||||
if !util.FileExist(path) {
|
if !util.FileExist(path) {
|
||||||
|
dir, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
dir = strings.ReplaceAll(dir, "\\", "/")
|
||||||
|
errorText := fmt.Sprintf("The Casdoor frontend HTML file: \"index.html\" was not found, it should be placed at: \"%s/web/build/index.html\". For more information, see: https://casdoor.org/docs/basic/server-installation/#frontend-1", dir)
|
||||||
|
http.ServeContent(ctx.ResponseWriter, ctx.Request, "Casdoor frontend has encountered error...", time.Now(), strings.NewReader(errorText))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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":
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -186,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 {
|
||||||
@ -315,3 +290,13 @@ func ParseIdToString(input interface{}) (string, error) {
|
|||||||
return "", fmt.Errorf("unsupported id type: %T", input)
|
return "", fmt.Errorf("unsupported id type: %T", input)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetValueFromDataSourceName(key string, dataSourceName string) string {
|
||||||
|
reg := regexp.MustCompile(key + "=([^ ]+)")
|
||||||
|
matches := reg.FindStringSubmatch(dataSourceName)
|
||||||
|
if len(matches) >= 2 {
|
||||||
|
return matches[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
@ -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,6 +101,21 @@ class PaymentListPage extends BaseListPage {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Organization"),
|
||||||
|
dataIndex: "owner",
|
||||||
|
key: "owner",
|
||||||
|
width: "120px",
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps("owner"),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Link to={`/organizations/${text}`}>
|
||||||
|
{text}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("general:Provider"),
|
title: i18next.t("general:Provider"),
|
||||||
dataIndex: "provider",
|
dataIndex: "provider",
|
||||||
@ -117,21 +132,6 @@ class PaymentListPage extends BaseListPage {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: i18next.t("general:Organization"),
|
|
||||||
dataIndex: "owner",
|
|
||||||
key: "owner",
|
|
||||||
width: "120px",
|
|
||||||
sorter: true,
|
|
||||||
...this.getColumnSearchProps("owner"),
|
|
||||||
render: (text, record, index) => {
|
|
||||||
return (
|
|
||||||
<Link to={`/organizations/${text}`}>
|
|
||||||
{text}
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: i18next.t("general:User"),
|
title: i18next.t("general:User"),
|
||||||
dataIndex: "user",
|
dataIndex: "user",
|
||||||
@ -264,7 +264,7 @@ class PaymentListPage extends BaseListPage {
|
|||||||
value = params.type;
|
value = params.type;
|
||||||
}
|
}
|
||||||
this.setState({loading: true});
|
this.setState({loading: true});
|
||||||
PaymentBackend.getPayments(Setting.getRequestOrganization(this.props.account), Setting.isDefaultOrganizationSelected(this.props.account) ? "" : Setting.getRequestOrganization(this.props.account), params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
|
PaymentBackend.getPayments(Setting.isDefaultOrganizationSelected(this.props.account) ? "" : Setting.getRequestOrganization(this.props.account), params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false,
|
loading: false,
|
||||||
|
@ -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",
|
||||||
|
@ -197,22 +197,27 @@ class PlanEditPage extends React.Component {
|
|||||||
</Row>
|
</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("plan:Price per month"), i18next.t("plan:Price per month - Tooltip"))} :
|
{Setting.getLabel(i18next.t("plan:Price"), i18next.t("plan:Price - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<InputNumber value={this.state.plan.pricePerMonth} onChange={value => {
|
<InputNumber value={this.state.plan.price} onChange={value => {
|
||||||
this.updatePlanField("pricePerMonth", value);
|
this.updatePlanField("price", value);
|
||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</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("plan:Price per year"), i18next.t("plan:Price per year - Tooltip"))} :
|
{Setting.getLabel(i18next.t("plan:Period"), i18next.t("plan:Period - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<InputNumber value={this.state.plan.pricePerYear} onChange={value => {
|
<Select virtual={false} style={{width: "100%"}} value={this.state.plan.period} onChange={value => {
|
||||||
this.updatePlanField("pricePerYear", value);
|
this.updatePlanField("period", value);
|
||||||
}} />
|
}}
|
||||||
|
options={[
|
||||||
|
{value: "Monthly", label: "Monthly"},
|
||||||
|
{value: "Yearly", label: "Yearly"},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
@ -32,9 +32,9 @@ class PlanListPage extends BaseListPage {
|
|||||||
createdTime: moment().format(),
|
createdTime: moment().format(),
|
||||||
displayName: `New Plan - ${randomName}`,
|
displayName: `New Plan - ${randomName}`,
|
||||||
description: "",
|
description: "",
|
||||||
pricePerMonth: 10,
|
price: 10,
|
||||||
pricePerYear: 100,
|
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
|
period: "Monthly",
|
||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
paymentProviders: [],
|
paymentProviders: [],
|
||||||
role: "",
|
role: "",
|
||||||
@ -136,18 +136,18 @@ class PlanListPage extends BaseListPage {
|
|||||||
...this.getColumnSearchProps("currency"),
|
...this.getColumnSearchProps("currency"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("plan:Price per month"),
|
title: i18next.t("plan:Price"),
|
||||||
dataIndex: "pricePerMonth",
|
dataIndex: "price",
|
||||||
key: "pricePerMonth",
|
key: "price",
|
||||||
width: "130px",
|
width: "130px",
|
||||||
...this.getColumnSearchProps("pricePerMonth"),
|
...this.getColumnSearchProps("price"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("plan:Price per year"),
|
title: i18next.t("plan:Period"),
|
||||||
dataIndex: "pricePerYear",
|
dataIndex: "period",
|
||||||
key: "pricePerYear",
|
key: "period",
|
||||||
width: "130px",
|
width: "130px",
|
||||||
...this.getColumnSearchProps("pricePerYear"),
|
...this.getColumnSearchProps("period"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("general:Role"),
|
title: i18next.t("general:Role"),
|
||||||
|
@ -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,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,11 +74,10 @@ class ProductBuyPage extends React.Component {
|
|||||||
throw new Error(res.msg);
|
throw new Error(res.msg);
|
||||||
}
|
}
|
||||||
const plan = res.data;
|
const plan = res.data;
|
||||||
const productName = plan.product;
|
|
||||||
await this.setStateAsync({
|
await this.setStateAsync({
|
||||||
pricing: pricing,
|
pricing: pricing,
|
||||||
plan: plan,
|
plan: plan,
|
||||||
productName: productName,
|
productName: plan.product,
|
||||||
});
|
});
|
||||||
this.onUpdatePricing(pricing);
|
this.onUpdatePricing(pricing);
|
||||||
}
|
}
|
||||||
@ -131,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,
|
||||||
});
|
});
|
||||||
@ -145,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}`);
|
||||||
@ -160,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") {
|
||||||
@ -291,9 +246,6 @@ class ProductBuyPage extends React.Component {
|
|||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
</Descriptions>
|
</Descriptions>
|
||||||
</Spin>
|
</Spin>
|
||||||
{
|
|
||||||
this.renderQrCodeModal()
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -98,6 +98,7 @@ class ProductEditPage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderProduct() {
|
renderProduct() {
|
||||||
|
const isCreatedByPlan = this.state.product.tag === "auto_created_product_for_plan";
|
||||||
return (
|
return (
|
||||||
<Card size="small" title={
|
<Card size="small" title={
|
||||||
<div>
|
<div>
|
||||||
@ -107,12 +108,24 @@ class ProductEditPage extends React.Component {
|
|||||||
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteProduct()}>{i18next.t("general:Cancel")}</Button> : null}
|
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteProduct()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||||
</div>
|
</div>
|
||||||
} style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
|
} style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
|
||||||
|
<Row style={{marginTop: "10px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: "100%"}} disabled={!Setting.isAdminUser(this.props.account) || isCreatedByPlan} value={this.state.product.owner} onChange={(value => {this.updateProductField("owner", value);})}>
|
||||||
|
{
|
||||||
|
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</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("general:Name"), i18next.t("general:Name - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Input value={this.state.product.name} onChange={e => {
|
<Input value={this.state.product.name} disabled={isCreatedByPlan} onChange={e => {
|
||||||
this.updateProductField("name", e.target.value);
|
this.updateProductField("name", e.target.value);
|
||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
@ -127,18 +140,6 @@ class ProductEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row style={{marginTop: "20px"}} >
|
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
|
||||||
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
|
||||||
</Col>
|
|
||||||
<Col span={22} >
|
|
||||||
<Select virtual={false} style={{width: "100%"}} disabled={!Setting.isAdminUser(this.props.account)} value={this.state.product.owner} onChange={(value => {this.updateProductField("owner", value);})}>
|
|
||||||
{
|
|
||||||
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
|
|
||||||
}
|
|
||||||
</Select>
|
|
||||||
</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("product:Image"), i18next.t("product:Image - Tooltip"))} :
|
{Setting.getLabel(i18next.t("product:Image"), i18next.t("product:Image - Tooltip"))} :
|
||||||
@ -171,7 +172,7 @@ class ProductEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("user:Tag"), i18next.t("product:Tag - Tooltip"))} :
|
{Setting.getLabel(i18next.t("user:Tag"), i18next.t("product:Tag - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Input value={this.state.product.tag} onChange={e => {
|
<Input value={this.state.product.tag} disabled={isCreatedByPlan} onChange={e => {
|
||||||
this.updateProductField("tag", e.target.value);
|
this.updateProductField("tag", e.target.value);
|
||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
@ -201,7 +202,7 @@ class ProductEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("payment:Currency"), i18next.t("payment:Currency - Tooltip"))} :
|
{Setting.getLabel(i18next.t("payment:Currency"), i18next.t("payment:Currency - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Select virtual={false} style={{width: "100%"}} value={this.state.product.currency} onChange={(value => {
|
<Select virtual={false} style={{width: "100%"}} value={this.state.product.currency} disabled={isCreatedByPlan} onChange={(value => {
|
||||||
this.updateProductField("currency", value);
|
this.updateProductField("currency", value);
|
||||||
})}>
|
})}>
|
||||||
{
|
{
|
||||||
@ -218,7 +219,7 @@ class ProductEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("product:Price"), i18next.t("product:Price - Tooltip"))} :
|
{Setting.getLabel(i18next.t("product:Price"), i18next.t("product:Price - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<InputNumber value={this.state.product.price} onChange={value => {
|
<InputNumber value={this.state.product.price} disabled={isCreatedByPlan} onChange={value => {
|
||||||
this.updateProductField("price", value);
|
this.updateProductField("price", value);
|
||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
@ -228,7 +229,7 @@ class ProductEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("product:Quantity"), i18next.t("product:Quantity - Tooltip"))} :
|
{Setting.getLabel(i18next.t("product:Quantity"), i18next.t("product:Quantity - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<InputNumber value={this.state.product.quantity} onChange={value => {
|
<InputNumber value={this.state.product.quantity} disabled={isCreatedByPlan} onChange={value => {
|
||||||
this.updateProductField("quantity", value);
|
this.updateProductField("quantity", value);
|
||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
@ -238,7 +239,7 @@ class ProductEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("product:Sold"), i18next.t("product:Sold - Tooltip"))} :
|
{Setting.getLabel(i18next.t("product:Sold"), i18next.t("product:Sold - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<InputNumber value={this.state.product.sold} onChange={value => {
|
<InputNumber value={this.state.product.sold} disabled={isCreatedByPlan} onChange={value => {
|
||||||
this.updateProductField("sold", value);
|
this.updateProductField("sold", value);
|
||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
@ -248,7 +249,7 @@ class ProductEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("product:Payment providers"), i18next.t("product:Payment providers - Tooltip"))} :
|
{Setting.getLabel(i18next.t("product:Payment providers"), i18next.t("product:Payment providers - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Select virtual={false} mode="multiple" style={{width: "100%"}} value={this.state.product.providers} onChange={(value => {this.updateProductField("providers", value);})}>
|
<Select virtual={false} mode="multiple" style={{width: "100%"}} disabled={isCreatedByPlan} value={this.state.product.providers} onChange={(value => {this.updateProductField("providers", value);})}>
|
||||||
{
|
{
|
||||||
this.state.providers.map((provider, index) => <Option key={index} value={provider.name}>{provider.name}</Option>)
|
this.state.providers.map((provider, index) => <Option key={index} value={provider.name}>{provider.name}</Option>)
|
||||||
}
|
}
|
||||||
@ -312,7 +313,7 @@ class ProductEditPage extends React.Component {
|
|||||||
|
|
||||||
submitProductEdit(willExist) {
|
submitProductEdit(willExist) {
|
||||||
const product = Setting.deepCopy(this.state.product);
|
const product = Setting.deepCopy(this.state.product);
|
||||||
ProductBackend.updateProduct(this.state.product.owner, this.state.productName, product)
|
ProductBackend.updateProduct(this.state.organizationName, this.state.productName, product)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully saved"));
|
Setting.showMessage("success", i18next.t("general:Successfully saved"));
|
||||||
|
@ -253,11 +253,13 @@ class ProductListPage extends BaseListPage {
|
|||||||
width: "230px",
|
width: "230px",
|
||||||
fixed: (Setting.isMobile()) ? "false" : "right",
|
fixed: (Setting.isMobile()) ? "false" : "right",
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
|
const isCreatedByPlan = record.tag === "auto_created_product_for_plan";
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} onClick={() => this.props.history.push(`/products/${record.owner}/${record.name}/buy`)}>{i18next.t("product:Buy")}</Button>
|
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} onClick={() => this.props.history.push(`/products/${record.owner}/${record.name}/buy`)}>{i18next.t("product:Buy")}</Button>
|
||||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/products/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/products/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||||
<PopconfirmModal
|
<PopconfirmModal
|
||||||
|
disabled={isCreatedByPlan}
|
||||||
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
||||||
onConfirm={() => this.deleteProduct(index)}
|
onConfirm={() => this.deleteProduct(index)}
|
||||||
>
|
>
|
||||||
|
@ -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;
|
@ -32,7 +32,8 @@ export const ServerUrl = "";
|
|||||||
|
|
||||||
export const StaticBaseUrl = "https://cdn.casbin.org";
|
export const StaticBaseUrl = "https://cdn.casbin.org";
|
||||||
|
|
||||||
export const Countries = [{label: "English", key: "en", country: "US", alt: "English"},
|
export const Countries = [
|
||||||
|
{label: "English", key: "en", country: "US", alt: "English"},
|
||||||
{label: "Español", key: "es", country: "ES", alt: "Español"},
|
{label: "Español", key: "es", country: "ES", alt: "Español"},
|
||||||
{label: "Français", key: "fr", country: "FR", alt: "Français"},
|
{label: "Français", key: "fr", country: "FR", alt: "Français"},
|
||||||
{label: "Deutsch", key: "de", country: "DE", alt: "Deutsch"},
|
{label: "Deutsch", key: "de", country: "DE", alt: "Deutsch"},
|
||||||
@ -42,13 +43,19 @@ export const Countries = [{label: "English", key: "en", country: "US", alt: "Eng
|
|||||||
{label: "한국어", key: "ko", country: "KR", alt: "한국어"},
|
{label: "한국어", key: "ko", country: "KR", alt: "한국어"},
|
||||||
{label: "Русский", key: "ru", country: "RU", alt: "Русский"},
|
{label: "Русский", key: "ru", country: "RU", alt: "Русский"},
|
||||||
{label: "TiếngViệt", key: "vi", country: "VN", alt: "TiếngViệt"},
|
{label: "TiếngViệt", key: "vi", country: "VN", alt: "TiếngViệt"},
|
||||||
{label: "Português", key: "pt", country: "BR", alt: "Português"},
|
{label: "Português", key: "pt", country: "PT", alt: "Português"},
|
||||||
{label: "Itariano", key: "it", country: "IT", alt: "Itariano"},
|
{label: "Italiano", key: "it", country: "IT", alt: "Italiano"},
|
||||||
{label: "Marley", key: "ms", country: "MY", alt: "Marley"},
|
{label: "Malay", key: "ms", country: "MY", alt: "Malay"},
|
||||||
{label: "Tkiš", key: "tr", country: "TR", alt: "Tkiš"},
|
{label: "Türkçe", key: "tr", country: "TR", alt: "Türkçe"},
|
||||||
{label: "لغة عربية", key: "ar", country: "DZ", alt: "لغة عربية"},
|
{label: "لغة عربية", key: "ar", country: "SA", alt: "لغة عربية"},
|
||||||
{label: "עִבְרִית", key: "he", country: "IL", alt: "עִבְרִית"},
|
{label: "עִבְרִית", key: "he", country: "IL", alt: "עִבְרִית"},
|
||||||
{label: "Filipino", key: "fi", country: "PH", alt: "Filipino"},
|
{label: "Nederlands", key: "nl", country: "NL", alt: "Nederlands"},
|
||||||
|
{label: "Polski", key: "pl", country: "PL", alt: "Polski"},
|
||||||
|
{label: "Suomi", key: "fi", country: "FI", alt: "Suomi"},
|
||||||
|
{label: "Svenska", key: "sv", country: "SE", alt: "Svenska"},
|
||||||
|
{label: "Українська", key: "uk", country: "UA", alt: "Українська"},
|
||||||
|
{label: "Қазақ", key: "kk", country: "KZ", alt: "Қазақ"},
|
||||||
|
{label: "فارسی", key: "fa", country: "IR", alt: "فارسی"},
|
||||||
];
|
];
|
||||||
|
|
||||||
export function getThemeData(organization, application) {
|
export function getThemeData(organization, application) {
|
||||||
@ -154,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": {
|
||||||
@ -276,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/",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -901,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") {
|
||||||
@ -966,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 [];
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Button, Card, Col, DatePicker, Input, InputNumber, Row, Select} from "antd";
|
import {Button, Card, Col, DatePicker, Input, Row, Select} from "antd";
|
||||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||||
import * as PricingBackend from "./backend/PricingBackend";
|
import * as PricingBackend from "./backend/PricingBackend";
|
||||||
import * as PlanBackend from "./backend/PlanBackend";
|
import * as PlanBackend from "./backend/PlanBackend";
|
||||||
@ -171,16 +171,6 @@ class SubscriptionEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row style={{marginTop: "20px"}} >
|
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
|
||||||
{Setting.getLabel(i18next.t("subscription:Duration"), i18next.t("subscription:Duration - Tooltip"))}
|
|
||||||
</Col>
|
|
||||||
<Col span={22} >
|
|
||||||
<InputNumber value={this.state.subscription.duration} onChange={value => {
|
|
||||||
this.updateSubscriptionField("duration", value);
|
|
||||||
}} />
|
|
||||||
</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("subscription:Start time"), i18next.t("subscription:Start time - Tooltip"))}
|
{Setting.getLabel(i18next.t("subscription:Start time"), i18next.t("subscription:Start time - Tooltip"))}
|
||||||
@ -201,6 +191,23 @@ class SubscriptionEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("plan:Period"), i18next.t("plan:Period - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select
|
||||||
|
defaultValue={this.state.subscription.period === "" ? "Monthly" : this.state.subscription.period}
|
||||||
|
onChange={value => {
|
||||||
|
this.updateSubscriptionField("period", value);
|
||||||
|
}}
|
||||||
|
options={[
|
||||||
|
{value: "Monthly", label: "Monthly"},
|
||||||
|
{value: "Yearly", label: "Yearly"},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</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("general:User"), i18next.t("general:User - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:User"), i18next.t("general:User - Tooltip"))} :
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user