mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-24 07:03:21 +08:00
Compare commits
64 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
7a85b74573 | |||
7e349c1768 | |||
b19be2df88 | |||
fc3866db1c | |||
bf2bb31e41 | |||
ec8bd6f01d | |||
98722fd681 | |||
221c55aa93 | |||
988b26b3c2 | |||
7e3c361ce7 | |||
a637707e77 | |||
7970edeaa7 | |||
9da2f0775f | |||
739a9bcd0d | |||
fb0949b9ed | |||
27ed901167 | |||
ceab662b88 | |||
05b2f00057 | |||
8073dfa88c | |||
1eeeb64a0c | |||
f5e0461cae |
@ -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/version_info.txt ./go/src/casdoor/version_info.txt
|
||||
COPY --from=FRONT /web/build ./web/build
|
||||
RUN mkdir tempFiles
|
||||
|
||||
ENTRYPOINT ["/bin/bash"]
|
||||
CMD ["/docker-entrypoint.sh"]
|
||||
|
@ -87,6 +87,8 @@ p, *, *, GET, /api/get-prometheus-info, *, *
|
||||
p, *, *, *, /api/metrics, *, *
|
||||
p, *, *, GET, /api/get-pricing, *, *
|
||||
p, *, *, GET, /api/get-plan, *, *
|
||||
p, *, *, GET, /api/get-subscription, *, *
|
||||
p, *, *, GET, /api/get-provider, *, *
|
||||
p, *, *, GET, /api/get-organization-names, *, *
|
||||
`
|
||||
|
||||
@ -119,6 +121,10 @@ func IsAllowed(subOwner string, subName string, method string, urlPath string, o
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if subOwner == "app" {
|
||||
return true
|
||||
}
|
||||
|
||||
if user != nil && user.IsAdmin && (subOwner == objOwner || (objOwner == "admin")) {
|
||||
return true
|
||||
}
|
||||
|
@ -140,25 +140,28 @@ func (c *ApiController) Signup() {
|
||||
username = id
|
||||
}
|
||||
|
||||
password := authForm.Password
|
||||
msg = object.CheckPasswordComplexityByOrg(organization, password)
|
||||
if msg != "" {
|
||||
c.ResponseError(msg)
|
||||
return
|
||||
}
|
||||
|
||||
initScore, err := organization.GetInitScore()
|
||||
if err != nil {
|
||||
c.ResponseError(fmt.Errorf(c.T("account:Get init score failed, error: %w"), err).Error())
|
||||
return
|
||||
}
|
||||
|
||||
userType := "normal-user"
|
||||
if authForm.Plan != "" && authForm.Pricing != "" {
|
||||
err = object.CheckPricingAndPlan(authForm.Organization, authForm.Pricing, authForm.Plan)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
userType = "paid-user"
|
||||
}
|
||||
|
||||
user := &object.User{
|
||||
Owner: authForm.Organization,
|
||||
Name: username,
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
Id: id,
|
||||
Type: "normal-user",
|
||||
Type: userType,
|
||||
Password: authForm.Password,
|
||||
DisplayName: authForm.Name,
|
||||
Avatar: organization.DefaultAvatar,
|
||||
@ -210,7 +213,7 @@ func (c *ApiController) Signup() {
|
||||
return
|
||||
}
|
||||
|
||||
if application.HasPromptPage() {
|
||||
if application.HasPromptPage() && user.Type == "normal-user" {
|
||||
// The prompt page needs the user to be signed in
|
||||
c.SetSessionUsername(user.GetId())
|
||||
}
|
||||
@ -227,15 +230,6 @@ func (c *ApiController) Signup() {
|
||||
return
|
||||
}
|
||||
|
||||
isSignupFromPricing := authForm.Plan != "" && authForm.Pricing != ""
|
||||
if isSignupFromPricing {
|
||||
_, err = object.Subscribe(organization.Name, user.Name, authForm.Plan, authForm.Pricing)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
record := object.NewRecord(c.Ctx)
|
||||
record.Organization = application.Organization
|
||||
record.User = user.Name
|
||||
|
@ -62,13 +62,13 @@ func (c *ApiController) GetApplications() {
|
||||
}
|
||||
|
||||
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 {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
applications := object.GetMaskedApplications(app, userId)
|
||||
applications := object.GetMaskedApplications(application, userId)
|
||||
c.ResponseOk(applications, paginator.Nums())
|
||||
}
|
||||
}
|
||||
@ -84,13 +84,23 @@ func (c *ApiController) GetApplication() {
|
||||
userId := c.GetSessionUsername()
|
||||
id := c.Input().Get("id")
|
||||
|
||||
app, err := object.GetApplication(id)
|
||||
application, err := object.GetApplication(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(object.GetMaskedApplication(app, userId))
|
||||
if c.Input().Get("withKey") != "" && application.Cert != "" {
|
||||
cert, err := object.GetCert(util.GetId(application.Owner, application.Cert))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
application.CertPublicKey = cert.Certificate
|
||||
}
|
||||
|
||||
c.ResponseOk(object.GetMaskedApplication(application, userId))
|
||||
}
|
||||
|
||||
// GetUserApplication
|
||||
@ -164,13 +174,13 @@ func (c *ApiController) GetOrganizationApplications() {
|
||||
}
|
||||
|
||||
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 {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
applications := object.GetMaskedApplications(app, userId)
|
||||
applications := object.GetMaskedApplications(application, userId)
|
||||
c.ResponseOk(applications, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
@ -78,6 +78,46 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
|
||||
}
|
||||
}
|
||||
|
||||
// check whether paid-user have active subscription
|
||||
if user.Type == "paid-user" {
|
||||
subscriptions, err := object.GetSubscriptionsByUser(user.Owner, user.Name)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
existActiveSubscription := false
|
||||
for _, subscription := range subscriptions {
|
||||
if subscription.State == object.SubStateActive {
|
||||
existActiveSubscription = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !existActiveSubscription {
|
||||
// check pending subscription
|
||||
for _, sub := range subscriptions {
|
||||
if sub.State == object.SubStatePending {
|
||||
c.ResponseOk("BuyPlanResult", sub)
|
||||
return
|
||||
}
|
||||
}
|
||||
// paid-user does not have active or pending subscription, find the default pricing of application
|
||||
pricing, err := object.GetApplicationDefaultPricing(application.Organization, application.Name)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if pricing == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"), user.Name, application.Name))
|
||||
return
|
||||
} else {
|
||||
// let the paid-user select plan
|
||||
c.ResponseOk("SelectPlan", pricing)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if form.Type == ResponseTypeLogin {
|
||||
c.SetSessionUsername(userId)
|
||||
util.LogInfo(c.Ctx, "API: [%s] signed in", userId)
|
||||
|
@ -61,6 +61,10 @@ func (c *ApiController) IsAdminOrSelf(user2 *object.User) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
if user == nil || user2 == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if user.Owner == user2.Owner && user.Name == user2.Name {
|
||||
return true
|
||||
}
|
||||
|
@ -35,6 +35,11 @@ const (
|
||||
UnauthorizedService string = "UNAUTHORIZED_SERVICE"
|
||||
)
|
||||
|
||||
func queryUnescape(service string) string {
|
||||
s, _ := url.QueryUnescape(service)
|
||||
return s
|
||||
}
|
||||
|
||||
func (c *RootController) CasValidate() {
|
||||
ticket := c.Input().Get("ticket")
|
||||
service := c.Input().Get("service")
|
||||
@ -60,24 +65,25 @@ func (c *RootController) CasServiceValidate() {
|
||||
if !strings.HasPrefix(ticket, "ST") {
|
||||
c.sendCasAuthenticationResponseErr(InvalidTicket, fmt.Sprintf("Ticket %s not recognized", ticket), format)
|
||||
}
|
||||
c.CasP3ServiceAndProxyValidate()
|
||||
c.CasP3ProxyValidate()
|
||||
}
|
||||
|
||||
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")
|
||||
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.CasP3ServiceAndProxyValidate()
|
||||
c.CasP3ProxyValidate()
|
||||
}
|
||||
|
||||
func queryUnescape(service string) string {
|
||||
s, _ := url.QueryUnescape(service)
|
||||
return s
|
||||
}
|
||||
|
||||
func (c *RootController) CasP3ServiceAndProxyValidate() {
|
||||
func (c *RootController) CasP3ProxyValidate() {
|
||||
ticket := c.Input().Get("ticket")
|
||||
format := c.Input().Get("format")
|
||||
service := c.Input().Get("service")
|
||||
@ -115,15 +121,17 @@ func (c *RootController) CasP3ServiceAndProxyValidate() {
|
||||
pgtiou := serviceResponse.Success.ProxyGrantingTicket
|
||||
// todo: check whether it is https
|
||||
pgtUrlObj, err := url.Parse(pgtUrl)
|
||||
if err != nil {
|
||||
c.sendCasAuthenticationResponseErr(InvalidProxyCallback, err.Error(), format)
|
||||
return
|
||||
}
|
||||
|
||||
if pgtUrlObj.Scheme != "https" {
|
||||
c.sendCasAuthenticationResponseErr(InvalidProxyCallback, "callback is not https", format)
|
||||
return
|
||||
}
|
||||
|
||||
// make a request to pgturl passing pgt and pgtiou
|
||||
if err != nil {
|
||||
c.sendCasAuthenticationResponseErr(InternalError, err.Error(), format)
|
||||
return
|
||||
}
|
||||
param := pgtUrlObj.Query()
|
||||
param.Add("pgtId", pgt)
|
||||
param.Add("pgtIou", pgtiou)
|
||||
@ -263,7 +271,6 @@ func (c *RootController) sendCasAuthenticationResponseErr(code, msg, format stri
|
||||
Message: msg,
|
||||
},
|
||||
}
|
||||
|
||||
if format == "json" {
|
||||
c.Data["json"] = serviceResponse
|
||||
c.ServeJSON()
|
||||
|
@ -83,7 +83,7 @@ func (c *ApiController) GetEnforcer() {
|
||||
return
|
||||
}
|
||||
|
||||
if loadModelCfg == "true" {
|
||||
if loadModelCfg == "true" && enforcer.Model != "" {
|
||||
err := enforcer.LoadModelCfg()
|
||||
if err != nil {
|
||||
return
|
||||
|
@ -176,11 +176,10 @@ func (c *ApiController) DeletePayment() {
|
||||
func (c *ApiController) NotifyPayment() {
|
||||
owner := c.Ctx.Input.Param(":owner")
|
||||
paymentName := c.Ctx.Input.Param(":payment")
|
||||
orderId := c.Ctx.Input.Param("order")
|
||||
|
||||
body := c.Ctx.Input.RequestBody
|
||||
|
||||
payment, err := object.NotifyPayment(c.Ctx.Request, body, owner, paymentName, orderId)
|
||||
payment, err := object.NotifyPayment(c.Ctx.Request, body, owner, paymentName)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
|
@ -16,6 +16,7 @@ package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
@ -32,16 +33,15 @@ func (c *ApiController) UploadPermissions() {
|
||||
}
|
||||
|
||||
fileId := fmt.Sprintf("%s_%s_%s", owner, user, util.RemoveExt(header.Filename))
|
||||
|
||||
path := util.GetUploadXlsxPath(fileId)
|
||||
util.EnsureFileFolderExists(path)
|
||||
defer os.Remove(path)
|
||||
err = saveFile(path, &file)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
affected, err := object.UploadPermissions(owner, fileId)
|
||||
affected, err := object.UploadPermissions(owner, path)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/beego/beego/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
@ -82,7 +83,10 @@ func (c *ApiController) GetPlan() {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if plan == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("plan:The plan: %s does not exist"), id))
|
||||
return
|
||||
}
|
||||
if includeOption {
|
||||
options, err := object.GetPermissionsByRole(plan.Role)
|
||||
if err != nil {
|
||||
@ -110,14 +114,29 @@ func (c *ApiController) GetPlan() {
|
||||
// @router /update-plan [post]
|
||||
func (c *ApiController) UpdatePlan() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
owner := util.GetOwnerFromId(id)
|
||||
var plan object.Plan
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &plan)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if plan.Product != "" {
|
||||
productId := util.GetId(owner, plan.Product)
|
||||
product, err := object.GetProduct(productId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if product != nil {
|
||||
object.UpdateProductForPlan(&plan, product)
|
||||
_, err = object.UpdateProduct(productId, product)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
c.Data["json"] = wrapActionResponse(object.UpdatePlan(id, &plan))
|
||||
c.ServeJSON()
|
||||
}
|
||||
@ -136,7 +155,14 @@ func (c *ApiController) AddPlan() {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Create a related product for plan
|
||||
product := object.CreateProductForPlan(&plan)
|
||||
_, err = object.AddProduct(product)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
plan.Product = product.Name
|
||||
c.Data["json"] = wrapActionResponse(object.AddPlan(&plan))
|
||||
c.ServeJSON()
|
||||
}
|
||||
@ -155,7 +181,13 @@ func (c *ApiController) DeletePlan() {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if plan.Product != "" {
|
||||
_, err = object.DeleteProduct(&object.Product{Owner: plan.Owner, Name: plan.Product})
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
c.Data["json"] = wrapActionResponse(object.DeletePlan(&plan))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/beego/beego/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
@ -80,7 +81,10 @@ func (c *ApiController) GetPricing() {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if pricing == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("pricing:The pricing: %s does not exist"), id))
|
||||
return
|
||||
}
|
||||
c.ResponseOk(pricing)
|
||||
}
|
||||
|
||||
|
@ -161,10 +161,17 @@ func (c *ApiController) DeleteProduct() {
|
||||
// @router /buy-product [post]
|
||||
func (c *ApiController) BuyProduct() {
|
||||
id := c.Input().Get("id")
|
||||
providerName := c.Input().Get("providerName")
|
||||
host := c.Ctx.Request.Host
|
||||
|
||||
userId := c.GetSessionUsername()
|
||||
providerName := c.Input().Get("providerName")
|
||||
// buy `pricingName/planName` for `paidUserName`
|
||||
pricingName := c.Input().Get("pricingName")
|
||||
planName := c.Input().Get("planName")
|
||||
paidUserName := c.Input().Get("userName")
|
||||
owner, _ := util.GetOwnerAndNameFromId(id)
|
||||
userId := util.GetId(owner, paidUserName)
|
||||
if paidUserName == "" {
|
||||
userId = c.GetSessionUsername()
|
||||
}
|
||||
if userId == "" {
|
||||
c.ResponseError(c.T("general:Please login first"))
|
||||
return
|
||||
@ -175,17 +182,16 @@ func (c *ApiController) BuyProduct() {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), userId))
|
||||
return
|
||||
}
|
||||
|
||||
payUrl, orderId, err := object.BuyProduct(id, providerName, user, host)
|
||||
payment, err := object.BuyProduct(id, user, providerName, pricingName, planName, host)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(payUrl, orderId)
|
||||
c.ResponseOk(payment)
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
@ -32,16 +33,15 @@ func (c *ApiController) UploadRoles() {
|
||||
}
|
||||
|
||||
fileId := fmt.Sprintf("%s_%s_%s", owner, user, util.RemoveExt(header.Filename))
|
||||
|
||||
path := util.GetUploadXlsxPath(fileId)
|
||||
util.EnsureFileFolderExists(path)
|
||||
defer os.Remove(path)
|
||||
err = saveFile(path, &file)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
affected, err := object.UploadRoles(owner, fileId)
|
||||
affected, err := object.UploadRoles(owner, path)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
}
|
||||
|
@ -160,7 +160,11 @@ func (c *ApiController) RunSyncer() {
|
||||
return
|
||||
}
|
||||
|
||||
object.RunSyncer(syncer)
|
||||
err = object.RunSyncer(syncer)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk()
|
||||
}
|
||||
|
@ -47,19 +47,16 @@ func (c *ApiController) GetSystemInfo() {
|
||||
// @router /get-version-info [get]
|
||||
func (c *ApiController) GetVersionInfo() {
|
||||
versionInfo, err := util.GetVersionInfo()
|
||||
if versionInfo.Version != "" {
|
||||
c.ResponseOk(versionInfo)
|
||||
return
|
||||
}
|
||||
|
||||
versionInfo, err = util.GetVersionInfoFromFile()
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if versionInfo.Version == "" {
|
||||
versionInfo, err = util.GetVersionInfoFromFile()
|
||||
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.ResponseOk(versionInfo)
|
||||
}
|
||||
|
||||
|
@ -258,6 +258,13 @@ func (c *ApiController) UpdateUser() {
|
||||
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 != "" {
|
||||
c.ResponseError(msg)
|
||||
return
|
||||
@ -441,6 +448,10 @@ func (c *ApiController) SetPassword() {
|
||||
}
|
||||
|
||||
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 {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
|
@ -48,17 +48,17 @@ func (c *ApiController) UploadUsers() {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
fileId := fmt.Sprintf("%s_%s_%s", owner, user, util.RemoveExt(header.Filename))
|
||||
|
||||
fileId := fmt.Sprintf("%s_%s_%s", owner, user, util.RemoveExt(header.Filename))
|
||||
path := util.GetUploadXlsxPath(fileId)
|
||||
util.EnsureFileFolderExists(path)
|
||||
defer os.Remove(path)
|
||||
err = saveFile(path, &file)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
affected, err := object.UploadUsers(owner, fileId)
|
||||
affected, err := object.UploadUsers(owner, path)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
|
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)
|
||||
}
|
23
form/auth.go
23
form/auth.go
@ -17,17 +17,18 @@ package form
|
||||
type AuthForm struct {
|
||||
Type string `json:"type"`
|
||||
|
||||
Organization string `json:"organization"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Name string `json:"name"`
|
||||
FirstName string `json:"firstName"`
|
||||
LastName string `json:"lastName"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
Affiliation string `json:"affiliation"`
|
||||
IdCard string `json:"idCard"`
|
||||
Region string `json:"region"`
|
||||
Organization string `json:"organization"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Name string `json:"name"`
|
||||
FirstName string `json:"firstName"`
|
||||
LastName string `json:"lastName"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
Affiliation string `json:"affiliation"`
|
||||
IdCard string `json:"idCard"`
|
||||
Region string `json:"region"`
|
||||
InvitationCode string `json:"invitationCode"`
|
||||
|
||||
Application string `json:"application"`
|
||||
ClientId string `json:"clientId"`
|
||||
|
19
go.mod
19
go.mod
@ -6,14 +6,14 @@ require (
|
||||
github.com/Masterminds/squirrel v1.5.3
|
||||
github.com/RobotsAndPencils/go-saml v0.0.0-20170520135329-fb13cb52a46b
|
||||
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.44.4
|
||||
github.com/aws/aws-sdk-go v1.45.5
|
||||
github.com/beego/beego v1.12.12
|
||||
github.com/beevik/etree v1.1.0
|
||||
github.com/casbin/casbin v1.9.1 // indirect
|
||||
github.com/casbin/casbin/v2 v2.30.1
|
||||
github.com/casdoor/go-sms-sender v0.12.0
|
||||
github.com/casbin/casbin/v2 v2.37.0
|
||||
github.com/casdoor/go-sms-sender v0.14.0
|
||||
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/xorm-adapter/v3 v3.0.4
|
||||
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-webauthn/webauthn v0.6.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/lestrrat-go/jwx v1.2.21
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3
|
||||
github.com/markbates/goth v1.75.2
|
||||
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/nyaruka/phonenumbers v1.1.5
|
||||
github.com/pquerna/otp v1.4.0
|
||||
@ -60,10 +59,12 @@ require (
|
||||
github.com/xorm-io/core v0.7.4
|
||||
github.com/xorm-io/xorm v1.1.6
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
golang.org/x/crypto v0.11.0
|
||||
golang.org/x/net v0.13.0
|
||||
golang.org/x/oauth2 v0.10.0
|
||||
golang.org/x/crypto v0.12.0
|
||||
golang.org/x/net v0.14.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/square/go-jose.v2 v2.6.0
|
||||
maunium.net/go/mautrix v0.16.0
|
||||
modernc.org/sqlite v1.18.2
|
||||
)
|
||||
|
@ -38,7 +38,13 @@ func TestGenerateI18nFrontend(t *testing.T) {
|
||||
applyToOtherLanguage("frontend", "tr", data)
|
||||
applyToOtherLanguage("frontend", "ar", data)
|
||||
applyToOtherLanguage("frontend", "he", data)
|
||||
applyToOtherLanguage("frontend", "nl", data)
|
||||
applyToOtherLanguage("frontend", "pl", 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) {
|
||||
@ -60,5 +66,11 @@ func TestGenerateI18nBackend(t *testing.T) {
|
||||
applyToOtherLanguage("backend", "tr", data)
|
||||
applyToOtherLanguage("backend", "ar", data)
|
||||
applyToOtherLanguage("backend", "he", data)
|
||||
applyToOtherLanguage("backend", "nl", data)
|
||||
applyToOtherLanguage("backend", "pl", 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
|
||||
// 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 {
|
||||
UserId string `json:"user_id"`
|
||||
}
|
||||
|
||||
// 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) {
|
||||
params := url.Values{}
|
||||
params.Add("client_id", idp.Config.ClientID)
|
||||
|
@ -9,11 +9,11 @@
|
||||
"passwordType": "plain",
|
||||
"passwordSalt": "",
|
||||
"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": "",
|
||||
"defaultApplication": "",
|
||||
"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": "",
|
||||
"initScore": 2000,
|
||||
"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
|
||||
|
||||
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" {
|
||||
return NewTelegramProvider(appId, receiver)
|
||||
} else if typ == "Custom HTTP" {
|
||||
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
|
||||
|
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"
|
||||
|
||||
"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"
|
||||
"github.com/nikoksr/notify"
|
||||
"github.com/nikoksr/notify/service/telegram"
|
||||
)
|
||||
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
t := &telegram.Telegram{}
|
||||
t.SetClient(client)
|
||||
telegramSrv := &telegram.Telegram{}
|
||||
telegramSrv.SetClient(client)
|
||||
|
||||
chatId, err := strconv.ParseInt(chatIdStr, 10, 64)
|
||||
if err != nil {
|
||||
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 {
|
||||
var dataSourceName string
|
||||
|
||||
if adapter.builtInAdapter() {
|
||||
if adapter.isBuiltIn() {
|
||||
dataSourceName = conf.GetConfigString("dataSourceName")
|
||||
if adapter.DatabaseType == "mysql" {
|
||||
dataSourceName = dataSourceName + adapter.Database
|
||||
@ -183,6 +183,14 @@ func (adapter *Adapter) InitAdapter() error {
|
||||
|
||||
var err error
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -211,7 +219,7 @@ func adapterChangeTrigger(oldName string, newName string) error {
|
||||
return session.Commit()
|
||||
}
|
||||
|
||||
func (adapter *Adapter) builtInAdapter() bool {
|
||||
func (adapter *Adapter) isBuiltIn() bool {
|
||||
if adapter.Owner != "built-in" {
|
||||
return false
|
||||
}
|
||||
|
@ -57,7 +57,9 @@ type Application struct {
|
||||
SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"`
|
||||
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
|
||||
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
|
||||
CertPublicKey string `xorm:"-" json:"certPublicKey"`
|
||||
Tags []string `xorm:"mediumtext" json:"tags"`
|
||||
InvitationCodes []string `xorm:"varchar(200)" json:"invitationCodes"`
|
||||
|
||||
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
||||
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
|
||||
@ -311,6 +313,11 @@ func GetMaskedApplication(application *Application, userId string) *Application
|
||||
application.OrganizationObj.PasswordSalt = "***"
|
||||
}
|
||||
}
|
||||
|
||||
if application.InvitationCodes != nil {
|
||||
application.InvitationCodes = []string{"***"}
|
||||
}
|
||||
|
||||
return application
|
||||
}
|
||||
|
||||
@ -421,15 +428,14 @@ func (application *Application) GetId() string {
|
||||
}
|
||||
|
||||
func (application *Application) IsRedirectUriValid(redirectUri string) bool {
|
||||
isValid := false
|
||||
for _, targetUri := range application.RedirectUris {
|
||||
redirectUris := append([]string{"http://localhost:"}, application.RedirectUris...)
|
||||
for _, targetUri := range redirectUris {
|
||||
targetUriRegex := regexp.MustCompile(targetUri)
|
||||
if targetUriRegex.MatchString(redirectUri) || strings.Contains(redirectUri, targetUri) {
|
||||
isValid = true
|
||||
break
|
||||
return true
|
||||
}
|
||||
}
|
||||
return isValid
|
||||
return false
|
||||
}
|
||||
|
||||
func IsOriginAllowed(origin string) (bool, error) {
|
||||
|
@ -66,8 +66,11 @@ func CheckUserSignup(application *Application, organization *Organization, form
|
||||
}
|
||||
}
|
||||
|
||||
if len(form.Password) <= 5 {
|
||||
return i18n.Translate(lang, "check:Password must have at least 6 characters")
|
||||
if application.IsSignupItemVisible("Password") {
|
||||
msg := CheckPasswordComplexityByOrg(organization, form.Password)
|
||||
if msg != "" {
|
||||
return msg
|
||||
}
|
||||
}
|
||||
|
||||
if application.IsSignupItemVisible("Email") {
|
||||
@ -124,6 +127,18 @@ func CheckUserSignup(application *Application, organization *Organization, form
|
||||
}
|
||||
}
|
||||
|
||||
if len(application.InvitationCodes) > 0 {
|
||||
if form.InvitationCode == "" {
|
||||
if application.IsSignupItemRequired("Invitation code") {
|
||||
return i18n.Translate(lang, "check:Invitation code cannot be blank")
|
||||
}
|
||||
} else {
|
||||
if !util.InSlice(application.InvitationCodes, form.InvitationCode) {
|
||||
return i18n.Translate(lang, "check:Invitation code is invalid")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
@ -391,10 +406,6 @@ func CheckUsername(username string, 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 msg := CheckUsername(user.Name, lang); msg != "" {
|
||||
return msg
|
||||
@ -404,7 +415,7 @@ func CheckUpdateUser(oldUser, user *User, lang string) string {
|
||||
}
|
||||
}
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ package object
|
||||
import (
|
||||
"crypto/tls"
|
||||
|
||||
"github.com/casdoor/casdoor/email"
|
||||
"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 {
|
||||
dialer := getDialer(provider)
|
||||
|
||||
message := gomail.NewMessage()
|
||||
emailProvider := email.GetEmailProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.AppId, provider.Host, provider.Port, provider.DisableSsl)
|
||||
|
||||
fromAddress := provider.ClientId2
|
||||
if fromAddress == "" {
|
||||
@ -49,14 +48,7 @@ func SendEmail(provider *Provider, title string, content string, dest string, se
|
||||
fromName = sender
|
||||
}
|
||||
|
||||
message.SetAddressHeader("From", fromAddress, fromName)
|
||||
message.SetHeader("To", dest)
|
||||
message.SetHeader("Subject", title)
|
||||
message.SetBody("text/html", content)
|
||||
|
||||
message.SkipUsernameCheck = true
|
||||
|
||||
return dialer.DialAndSend(message)
|
||||
return emailProvider.Send(fromAddress, fromName, dest, title, content)
|
||||
}
|
||||
|
||||
// DailSmtpServer Dail Smtp server
|
||||
|
@ -81,12 +81,15 @@ func (mfa *TotpMfa) SetupVerify(ctx *context.Context, passcode string) error {
|
||||
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,
|
||||
Skew: 1,
|
||||
Digits: otp.DigitsSix,
|
||||
Algorithm: otp.AlgorithmSHA1,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result {
|
||||
return nil
|
||||
@ -125,7 +128,15 @@ func (mfa *TotpMfa) Enable(ctx *context.Context, user *User) 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 {
|
||||
return nil
|
||||
|
@ -18,12 +18,12 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/casdoor/casdoor/notification"
|
||||
"github.com/nikoksr/notify"
|
||||
"github.com/casdoor/notify"
|
||||
)
|
||||
|
||||
func getNotificationClient(provider *Provider) (notify.Notifier, error) {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -18,11 +18,14 @@ import (
|
||||
"database/sql"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/beego/beego"
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
xormadapter "github.com/casdoor/xorm-adapter/v3"
|
||||
_ "github.com/denisenkom/go-mssqldb" // db = mssql
|
||||
_ "github.com/go-sql-driver/mysql" // db = mysql
|
||||
@ -65,6 +68,17 @@ func InitConfig() {
|
||||
}
|
||||
|
||||
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 {
|
||||
err := createDatabaseForPostgres(conf.GetConfigString("driverName"), conf.GetConfigDataSourceName(), conf.GetConfigString("dbName"))
|
||||
if err != nil {
|
||||
@ -122,9 +136,14 @@ func NewAdapter(driverName string, dataSourceName string, dbName string) *Ormer
|
||||
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 {
|
||||
if driverName == "postgres" {
|
||||
db, err := sql.Open(driverName, dataSourceName)
|
||||
db, err := sql.Open(driverName, refineDataSourceNameForPostgres(dataSourceName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -136,6 +155,21 @@ func createDatabaseForPostgres(driverName string, dataSourceName string, dbName
|
||||
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
|
||||
} else {
|
||||
@ -168,6 +202,12 @@ func (a *Ormer) open() {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if a.driverName == "postgres" {
|
||||
schema := util.GetValueFromDataSourceName("search_path", dataSourceName)
|
||||
if schema != "" {
|
||||
engine.SetSchema(schema)
|
||||
}
|
||||
}
|
||||
|
||||
a.Engine = engine
|
||||
}
|
||||
|
@ -55,6 +55,7 @@ type Payment struct {
|
||||
// Order Info
|
||||
OutOrderId string `xorm:"varchar(100)" json:"outOrderId"`
|
||||
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"`
|
||||
Message string `xorm:"varchar(2000)" json:"message"`
|
||||
}
|
||||
@ -152,7 +153,7 @@ func DeletePayment(payment *Payment) (bool, error) {
|
||||
return affected != 0, nil
|
||||
}
|
||||
|
||||
func notifyPayment(request *http.Request, body []byte, owner string, paymentName string, orderId string) (*Payment, *pp.NotifyResult, error) {
|
||||
func notifyPayment(request *http.Request, body []byte, owner string, paymentName string) (*Payment, *pp.NotifyResult, error) {
|
||||
payment, err := getPayment(owner, paymentName)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@ -180,11 +181,7 @@ func notifyPayment(request *http.Request, body []byte, owner string, paymentName
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if orderId == "" {
|
||||
orderId = payment.OutOrderId
|
||||
}
|
||||
|
||||
notifyResult, err := pProvider.Notify(request, body, cert.AuthorityPublicKey, orderId)
|
||||
notifyResult, err := pProvider.Notify(request, body, cert.AuthorityPublicKey, payment.OutOrderId)
|
||||
if err != nil {
|
||||
return payment, nil, err
|
||||
}
|
||||
@ -205,8 +202,8 @@ func notifyPayment(request *http.Request, body []byte, owner string, paymentName
|
||||
return payment, notifyResult, nil
|
||||
}
|
||||
|
||||
func NotifyPayment(request *http.Request, body []byte, owner string, paymentName string, orderId string) (*Payment, error) {
|
||||
payment, notifyResult, err := notifyPayment(request, body, owner, paymentName, orderId)
|
||||
func NotifyPayment(request *http.Request, body []byte, owner string, paymentName string) (*Payment, error) {
|
||||
payment, notifyResult, err := notifyPayment(request, body, owner, paymentName)
|
||||
if payment != nil {
|
||||
if err != nil {
|
||||
payment.State = pp.PaymentStateError
|
||||
|
@ -30,6 +30,7 @@ type Permission struct {
|
||||
Description string `xorm:"varchar(100)" json:"description"`
|
||||
|
||||
Users []string `xorm:"mediumtext" json:"users"`
|
||||
Groups []string `xorm:"mediumtext" json:"groups"`
|
||||
Roles []string `xorm:"mediumtext" json:"roles"`
|
||||
Domains []string `xorm:"mediumtext" json:"domains"`
|
||||
|
||||
|
@ -79,9 +79,7 @@ func (p *Permission) setEnforcerAdapter(enforcer *casbin.Enforcer) error {
|
||||
}
|
||||
}
|
||||
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
|
||||
driverName := conf.GetConfigString("driverName")
|
||||
dataSourceName := conf.GetConfigRealDataSourceName(driverName)
|
||||
adapter, err := xormadapter.NewAdapterWithTableName(driverName, dataSourceName, tableName, tableNamePrefix, true)
|
||||
adapter, err := xormadapter.NewAdapterByEngineWithTableName(ormer.Engine, tableName, tableNamePrefix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -33,8 +33,8 @@ func getPermissionMap(owner string) (map[string]*Permission, error) {
|
||||
return m, err
|
||||
}
|
||||
|
||||
func UploadPermissions(owner string, fileId string) (bool, error) {
|
||||
table := xlsx.ReadXlsxFile(fileId)
|
||||
func UploadPermissions(owner string, path string) (bool, error) {
|
||||
table := xlsx.ReadXlsxFile(path)
|
||||
|
||||
oldUserMap, err := getPermissionMap(owner)
|
||||
if err != nil {
|
||||
|
@ -16,6 +16,7 @@ package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/xorm-io/core"
|
||||
@ -28,15 +29,39 @@ type Plan struct {
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
Description string `xorm:"varchar(100)" json:"description"`
|
||||
|
||||
PricePerMonth float64 `json:"pricePerMonth"`
|
||||
PricePerYear float64 `json:"pricePerYear"`
|
||||
Currency string `xorm:"varchar(100)" json:"currency"`
|
||||
IsEnabled bool `json:"isEnabled"`
|
||||
Price float64 `json:"price"`
|
||||
Currency string `xorm:"varchar(100)" json:"currency"`
|
||||
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
|
||||
IsEnabled bool `json:"isEnabled"`
|
||||
|
||||
Role string `xorm:"varchar(100)" json:"role"`
|
||||
Options []string `xorm:"-" json:"options"`
|
||||
}
|
||||
|
||||
const (
|
||||
PeriodMonthly = "Monthly"
|
||||
PeriodYearly = "Yearly"
|
||||
)
|
||||
|
||||
func (plan *Plan) GetId() string {
|
||||
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) {
|
||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||
return session.Count(&Plan{})
|
||||
@ -114,38 +139,3 @@ func DeletePlan(plan *Plan) (bool, error) {
|
||||
}
|
||||
return affected != 0, nil
|
||||
}
|
||||
|
||||
func (plan *Plan) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", plan.Owner, plan.Name)
|
||||
}
|
||||
|
||||
func Subscribe(owner string, user string, plan string, pricing string) (*Subscription, error) {
|
||||
selectedPricing, err := GetPricing(fmt.Sprintf("%s/%s", owner, pricing))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
valid := selectedPricing != nil && selectedPricing.IsEnabled
|
||||
|
||||
if !valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
planBelongToPricing, err := selectedPricing.HasPlan(owner, plan)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if planBelongToPricing {
|
||||
newSubscription := NewSubscription(owner, user, plan, selectedPricing.TrialDuration)
|
||||
affected, err := AddSubscription(newSubscription)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if affected {
|
||||
return newSubscription, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/xorm-io/core"
|
||||
@ -33,12 +32,26 @@ type Pricing struct {
|
||||
IsEnabled bool `json:"isEnabled"`
|
||||
TrialDuration int `json:"trialDuration"`
|
||||
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"`
|
||||
func (pricing *Pricing) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", pricing.Owner, pricing.Name)
|
||||
}
|
||||
|
||||
State string `xorm:"varchar(100)" json:"state"`
|
||||
func (pricing *Pricing) HasPlan(planName string) (bool, error) {
|
||||
planId := util.GetId(pricing.Owner, planName)
|
||||
plan, err := GetPlan(planId)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if plan == nil {
|
||||
return false, fmt.Errorf("plan: %s does not exist", planId)
|
||||
}
|
||||
|
||||
if util.InSlice(pricing.Plans, plan.Name) {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func GetPricingCount(owner, field, value string) (int64, error) {
|
||||
@ -74,7 +87,7 @@ func getPricing(owner, name string) (*Pricing, error) {
|
||||
pricing := Pricing{Owner: owner, Name: name}
|
||||
existed, err := ormer.Engine.Get(&pricing)
|
||||
if err != nil {
|
||||
return &pricing, err
|
||||
return nil, err
|
||||
}
|
||||
if existed {
|
||||
return &pricing, nil
|
||||
@ -88,6 +101,20 @@ func GetPricing(id string) (*Pricing, error) {
|
||||
return getPricing(owner, name)
|
||||
}
|
||||
|
||||
func GetApplicationDefaultPricing(owner, appName string) (*Pricing, error) {
|
||||
pricings := make([]*Pricing, 0, 1)
|
||||
err := ormer.Engine.Asc("created_time").Find(&pricings, &Pricing{Owner: owner, Application: appName})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, pricing := range pricings {
|
||||
if pricing.IsEnabled {
|
||||
return pricing, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func UpdatePricing(id string, pricing *Pricing) (bool, error) {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
if p, err := getPricing(owner, name); err != nil {
|
||||
@ -120,28 +147,21 @@ func DeletePricing(pricing *Pricing) (bool, error) {
|
||||
return affected != 0, nil
|
||||
}
|
||||
|
||||
func (pricing *Pricing) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", pricing.Owner, pricing.Name)
|
||||
}
|
||||
|
||||
func (pricing *Pricing) HasPlan(owner string, plan string) (bool, error) {
|
||||
selectedPlan, err := GetPlan(fmt.Sprintf("%s/%s", owner, plan))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if selectedPlan == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
result := false
|
||||
|
||||
for _, pricingPlan := range pricing.Plans {
|
||||
if strings.Contains(pricingPlan, selectedPlan.Name) {
|
||||
result = true
|
||||
break
|
||||
func CheckPricingAndPlan(owner, pricingName, planName string) error {
|
||||
pricingId := util.GetId(owner, pricingName)
|
||||
pricing, err := GetPricing(pricingId)
|
||||
if pricing == nil || err != nil {
|
||||
if pricing == nil && err == nil {
|
||||
err = fmt.Errorf("pricing: %s does not exist", pricingName)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
ok, err := pricing.HasPlan(planName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("pricing: %s does not have plan: %s", pricingName, planName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ type Product struct {
|
||||
Price float64 `json:"price"`
|
||||
Quantity int `json:"quantity"`
|
||||
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"`
|
||||
|
||||
State string `xorm:"varchar(100)" json:"state"`
|
||||
@ -141,59 +141,76 @@ func (product *Product) isValidProvider(provider *Provider) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (product *Product) getProvider(providerId string) (*Provider, error) {
|
||||
provider, err := getProvider(product.Owner, providerId)
|
||||
func (product *Product) getProvider(providerName string) (*Provider, error) {
|
||||
provider, err := getProvider(product.Owner, providerName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if provider == nil {
|
||||
return nil, fmt.Errorf("the payment provider: %s does not exist", providerId)
|
||||
return nil, fmt.Errorf("the payment provider: %s does not exist", providerName)
|
||||
}
|
||||
|
||||
if !product.isValidProvider(provider) {
|
||||
return nil, fmt.Errorf("the payment provider: %s is not valid for the product: %s", providerId, product.Name)
|
||||
return nil, fmt.Errorf("the payment provider: %s is not valid for the product: %s", providerName, product.Name)
|
||||
}
|
||||
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
func BuyProduct(id string, providerName string, user *User, host string) (string, string, error) {
|
||||
func BuyProduct(id string, user *User, providerName, pricingName, planName, host string) (*Payment, error) {
|
||||
product, err := GetProduct(id)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pProvider, _, err := provider.getPaymentProvider()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
owner := product.Owner
|
||||
productName := product.Name
|
||||
payerName := fmt.Sprintf("%s | %s", user.Name, user.DisplayName)
|
||||
paymentName := util.GenerateTimeId()
|
||||
paymentName := fmt.Sprintf("payment_%v", util.GenerateTimeId())
|
||||
productDisplayName := product.DisplayName
|
||||
|
||||
originFrontend, originBackend := getOriginFromHost(host)
|
||||
returnUrl := fmt.Sprintf("%s/payments/%s/%s/result", originFrontend, owner, paymentName)
|
||||
notifyUrl := fmt.Sprintf("%s/api/notify-payment/%s/%s", originBackend, owner, paymentName)
|
||||
// Create an Order and get the payUrl
|
||||
if user.Type == "paid-user" {
|
||||
// Create a subscription for `paid-user`
|
||||
if pricingName != "" && planName != "" {
|
||||
plan, err := GetPlan(util.GetId(owner, planName))
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
}
|
||||
// Create an OrderId and get the payUrl
|
||||
payUrl, orderId, err := pProvider.Pay(providerName, productName, payerName, paymentName, productDisplayName, product.Price, product.Currency, returnUrl, notifyUrl)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return nil, err
|
||||
}
|
||||
// Create a Payment linked with Product and Order
|
||||
payment := Payment{
|
||||
payment := &Payment{
|
||||
Owner: product.Owner,
|
||||
Name: paymentName,
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
@ -212,6 +229,7 @@ func BuyProduct(id string, providerName string, user *User, host string) (string
|
||||
|
||||
User: user.Name,
|
||||
PayUrl: payUrl,
|
||||
SuccessUrl: returnUrl,
|
||||
State: pp.PaymentStateCreated,
|
||||
OutOrderId: orderId,
|
||||
}
|
||||
@ -220,16 +238,15 @@ func BuyProduct(id string, providerName string, user *User, host string) (string
|
||||
payment.State = pp.PaymentStatePaid
|
||||
}
|
||||
|
||||
affected, err := AddPayment(&payment)
|
||||
affected, err := AddPayment(payment)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -252,3 +269,38 @@ func ExtendProductWithProviders(product *Product) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateProductForPlan(plan *Plan) *Product {
|
||||
product := &Product{
|
||||
Owner: plan.Owner,
|
||||
Name: fmt.Sprintf("product_%v", util.GetRandomName()),
|
||||
DisplayName: fmt.Sprintf("Product for Plan %v/%v/%v", plan.Name, plan.DisplayName, plan.Period),
|
||||
CreatedTime: plan.CreatedTime,
|
||||
|
||||
Image: "https://cdn.casbin.org/img/casdoor-logo_1185x256.png", // TODO
|
||||
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,
|
||||
Tag: "auto_created_product_for_plan",
|
||||
Price: plan.Price,
|
||||
Currency: plan.Currency,
|
||||
|
||||
Quantity: 999,
|
||||
Sold: 0,
|
||||
|
||||
Providers: plan.PaymentProviders,
|
||||
State: "Published",
|
||||
}
|
||||
if product.Providers == nil {
|
||||
product.Providers = []string{}
|
||||
}
|
||||
return product
|
||||
}
|
||||
|
||||
func UpdateProductForPlan(plan *Plan, product *Product) {
|
||||
product.Owner = plan.Owner
|
||||
product.DisplayName = fmt.Sprintf("Product for Plan %v/%v/%v", plan.Name, plan.DisplayName, plan.Period)
|
||||
product.Detail = fmt.Sprintf("This product was auto created for plan %v(%v), subscription period is %v", plan.Name, plan.DisplayName, plan.Period)
|
||||
product.Price = plan.Price
|
||||
product.Currency = plan.Currency
|
||||
product.Providers = plan.PaymentProviders
|
||||
}
|
||||
|
@ -33,8 +33,8 @@ func getRoleMap(owner string) (map[string]*Role, error) {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func UploadRoles(owner string, fileId string) (bool, error) {
|
||||
table := xlsx.ReadXlsxFile(fileId)
|
||||
func UploadRoles(owner string, path string) (bool, error) {
|
||||
table := xlsx.ReadXlsxFile(path)
|
||||
|
||||
oldUserMap, err := getRoleMap(owner)
|
||||
if err != nil {
|
||||
|
@ -18,47 +18,108 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/casdoor/casdoor/pp"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/xorm-io/core"
|
||||
)
|
||||
|
||||
const defaultStatus = "Pending"
|
||||
type SubscriptionState string
|
||||
|
||||
const (
|
||||
SubStatePending SubscriptionState = "Pending"
|
||||
SubStateError SubscriptionState = "Error"
|
||||
SubStateSuspended SubscriptionState = "Suspended" // suspended by the admin
|
||||
|
||||
SubStateActive SubscriptionState = "Active"
|
||||
SubStateUpcoming SubscriptionState = "Upcoming"
|
||||
SubStateExpired SubscriptionState = "Expired"
|
||||
)
|
||||
|
||||
type Subscription struct {
|
||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
Description string `xorm:"varchar(100)" json:"description"`
|
||||
|
||||
StartDate time.Time `json:"startDate"`
|
||||
EndDate time.Time `json:"endDate"`
|
||||
Duration int `json:"duration"`
|
||||
Description string `xorm:"varchar(100)" json:"description"`
|
||||
User string `xorm:"varchar(100)" json:"user"`
|
||||
Pricing string `xorm:"varchar(100)" json:"pricing"`
|
||||
Plan string `xorm:"varchar(100)" json:"plan"`
|
||||
Payment string `xorm:"varchar(100)" json:"payment"`
|
||||
|
||||
User string `xorm:"mediumtext" json:"user"`
|
||||
Plan string `xorm:"varchar(100)" json:"plan"`
|
||||
|
||||
IsEnabled bool `json:"isEnabled"`
|
||||
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"`
|
||||
StartTime time.Time `json:"startTime"`
|
||||
EndTime time.Time `json:"endTime"`
|
||||
Period string `xorm:"varchar(100)" json:"period"`
|
||||
State SubscriptionState `xorm:"varchar(100)" json:"state"`
|
||||
}
|
||||
|
||||
func NewSubscription(owner string, user string, plan string, duration int) *Subscription {
|
||||
func (sub *Subscription) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", sub.Owner, sub.Name)
|
||||
}
|
||||
|
||||
func (sub *Subscription) UpdateState() error {
|
||||
preState := sub.State
|
||||
// update subscription state by payment state
|
||||
if sub.State == SubStatePending {
|
||||
if sub.Payment == "" {
|
||||
return nil
|
||||
}
|
||||
payment, err := GetPayment(util.GetId(sub.Owner, sub.Payment))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if payment == nil {
|
||||
sub.Description = fmt.Sprintf("payment: %s does not exist", sub.Payment)
|
||||
sub.State = SubStateError
|
||||
} else {
|
||||
if payment.State == pp.PaymentStatePaid {
|
||||
sub.State = SubStateActive
|
||||
} else if payment.State != pp.PaymentStateCreated {
|
||||
// other states: Canceled, Timeout, Error
|
||||
sub.Description = fmt.Sprintf("payment: %s state is %v", sub.Payment, payment.State)
|
||||
sub.State = SubStateError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sub.State == SubStateActive || sub.State == SubStateUpcoming || sub.State == SubStateExpired {
|
||||
if sub.EndTime.Before(time.Now()) {
|
||||
sub.State = SubStateExpired
|
||||
} else if sub.StartTime.After(time.Now()) {
|
||||
sub.State = SubStateUpcoming
|
||||
} else {
|
||||
sub.State = SubStateActive
|
||||
}
|
||||
}
|
||||
|
||||
if preState != sub.State {
|
||||
_, err := UpdateSubscription(sub.GetId(), sub)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewSubscription(owner, userName, planName, paymentName, period string) *Subscription {
|
||||
startTime, endTime := GetDuration(period)
|
||||
id := util.GenerateId()[:6]
|
||||
return &Subscription{
|
||||
Name: "Subscription_" + id,
|
||||
DisplayName: "New Subscription - " + id,
|
||||
Owner: owner,
|
||||
User: owner + "/" + user,
|
||||
Plan: owner + "/" + plan,
|
||||
Name: "sub_" + id,
|
||||
DisplayName: "New Subscription - " + id,
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
State: defaultStatus,
|
||||
Duration: duration,
|
||||
StartDate: time.Now(),
|
||||
EndDate: time.Now().AddDate(0, 0, duration),
|
||||
|
||||
User: userName,
|
||||
Plan: planName,
|
||||
Payment: paymentName,
|
||||
|
||||
StartTime: startTime,
|
||||
EndTime: endTime,
|
||||
Period: period,
|
||||
State: SubStatePending, // waiting for payment complete
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,7 +134,28 @@ func GetSubscriptions(owner string) ([]*Subscription, error) {
|
||||
if err != nil {
|
||||
return subscriptions, err
|
||||
}
|
||||
for _, sub := range subscriptions {
|
||||
err = sub.UpdateState()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return subscriptions, nil
|
||||
}
|
||||
|
||||
func GetSubscriptionsByUser(owner, userName string) ([]*Subscription, error) {
|
||||
subscriptions := []*Subscription{}
|
||||
err := ormer.Engine.Desc("created_time").Find(&subscriptions, &Subscription{Owner: owner, User: userName})
|
||||
if err != nil {
|
||||
return subscriptions, err
|
||||
}
|
||||
// update subscription state
|
||||
for _, sub := range subscriptions {
|
||||
err = sub.UpdateState()
|
||||
if err != nil {
|
||||
return subscriptions, err
|
||||
}
|
||||
}
|
||||
return subscriptions, nil
|
||||
}
|
||||
|
||||
@ -84,7 +166,12 @@ func GetPaginationSubscriptions(owner string, offset, limit int, field, value, s
|
||||
if err != nil {
|
||||
return subscriptions, err
|
||||
}
|
||||
|
||||
for _, sub := range subscriptions {
|
||||
err = sub.UpdateState()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return subscriptions, nil
|
||||
}
|
||||
|
||||
@ -144,7 +231,3 @@ func DeleteSubscription(subscription *Subscription) (bool, error) {
|
||||
|
||||
return affected != 0, nil
|
||||
}
|
||||
|
||||
func (subscription *Subscription) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", subscription.Owner, subscription.Name)
|
||||
}
|
||||
|
@ -37,12 +37,13 @@ type Syncer struct {
|
||||
|
||||
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||
Type string `xorm:"varchar(100)" json:"type"`
|
||||
DatabaseType string `xorm:"varchar(100)" json:"databaseType"`
|
||||
SslMode string `xorm:"varchar(100)" json:"sslMode"`
|
||||
|
||||
Host string `xorm:"varchar(100)" json:"host"`
|
||||
Port int `json:"port"`
|
||||
User string `xorm:"varchar(100)" json:"user"`
|
||||
Password string `xorm:"varchar(100)" json:"password"`
|
||||
DatabaseType string `xorm:"varchar(100)" json:"databaseType"`
|
||||
Database string `xorm:"varchar(100)" json:"database"`
|
||||
Table string `xorm:"varchar(100)" json:"table"`
|
||||
TableColumns []*TableColumn `xorm:"mediumtext" json:"tableColumns"`
|
||||
@ -250,7 +251,7 @@ func (syncer *Syncer) getKey() string {
|
||||
return key
|
||||
}
|
||||
|
||||
func RunSyncer(syncer *Syncer) {
|
||||
func RunSyncer(syncer *Syncer) error {
|
||||
syncer.initAdapter()
|
||||
syncer.syncUsers()
|
||||
return syncer.syncUsers()
|
||||
}
|
||||
|
@ -52,11 +52,14 @@ func addSyncerJob(syncer *Syncer) error {
|
||||
|
||||
syncer.initAdapter()
|
||||
|
||||
syncer.syncUsers()
|
||||
err := syncer.syncUsers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
schedule := fmt.Sprintf("@every %ds", syncer.SyncInterval)
|
||||
cron := getCronMap(syncer.Name)
|
||||
_, err := cron.AddFunc(schedule, syncer.syncUsers)
|
||||
_, err = cron.AddFunc(schedule, syncer.syncUsersNoError)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -19,15 +19,15 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func (syncer *Syncer) syncUsers() {
|
||||
func (syncer *Syncer) syncUsers() error {
|
||||
if len(syncer.TableColumns) == 0 {
|
||||
return
|
||||
return fmt.Errorf("The syncer table columns should not be empty")
|
||||
}
|
||||
|
||||
fmt.Printf("Running syncUsers()..\n")
|
||||
|
||||
users, _, _ := syncer.getUserMap()
|
||||
oUsers, oUserMap, err := syncer.getOriginalUserMap()
|
||||
oUsers, _, err := syncer.getOriginalUserMap()
|
||||
if err != nil {
|
||||
fmt.Printf(err.Error())
|
||||
|
||||
@ -35,10 +35,8 @@ func (syncer *Syncer) syncUsers() {
|
||||
line := fmt.Sprintf("[%s] %s\n", timestamp, err.Error())
|
||||
_, err = updateSyncerErrorText(syncer, line)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Users: %d, oUsers: %d\n", len(users), len(oUsers))
|
||||
@ -55,6 +53,11 @@ func (syncer *Syncer) syncUsers() {
|
||||
myUsers[syncer.getUserValue(m, key)] = m
|
||||
}
|
||||
|
||||
myOUsers := map[string]*User{}
|
||||
for _, m := range oUsers {
|
||||
myOUsers[syncer.getUserValue(m, key)] = m
|
||||
}
|
||||
|
||||
newUsers := []*User{}
|
||||
for _, oUser := range oUsers {
|
||||
primary := syncer.getUserValue(oUser, key)
|
||||
@ -71,28 +74,30 @@ func (syncer *Syncer) syncUsers() {
|
||||
updatedUser := syncer.createUserFromOriginalUser(oUser, affiliationMap)
|
||||
updatedUser.Hash = oHash
|
||||
updatedUser.PreHash = oHash
|
||||
|
||||
fmt.Printf("Update from oUser to user: %v\n", updatedUser)
|
||||
_, err = syncer.updateUserForOriginalByFields(updatedUser, key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Update from oUser to user: %v\n", updatedUser)
|
||||
}
|
||||
} else {
|
||||
if user.PreHash == oHash {
|
||||
if !syncer.IsReadOnly {
|
||||
updatedOUser := syncer.createOriginalUserFromUser(user)
|
||||
|
||||
fmt.Printf("Update from user to oUser: %v\n", updatedOUser)
|
||||
_, err = syncer.updateUser(updatedOUser)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Update from user to oUser: %v\n", updatedOUser)
|
||||
}
|
||||
|
||||
// update preHash
|
||||
user.PreHash = user.Hash
|
||||
_, err = SetUserField(user, "pre_hash", user.PreHash)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if user.Hash == oHash {
|
||||
@ -100,17 +105,18 @@ func (syncer *Syncer) syncUsers() {
|
||||
user.PreHash = user.Hash
|
||||
_, err = SetUserField(user, "pre_hash", user.PreHash)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
updatedUser := syncer.createUserFromOriginalUser(oUser, affiliationMap)
|
||||
updatedUser.Hash = oHash
|
||||
updatedUser.PreHash = oHash
|
||||
|
||||
fmt.Printf("Update from oUser to user (2nd condition): %v\n", updatedUser)
|
||||
_, err = syncer.updateUserForOriginalByFields(updatedUser, key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Update from oUser to user (2nd condition): %v\n", updatedUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -118,20 +124,30 @@ func (syncer *Syncer) syncUsers() {
|
||||
}
|
||||
_, err = AddUsersInBatch(newUsers)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if !syncer.IsReadOnly {
|
||||
for _, user := range users {
|
||||
id := user.Id
|
||||
if _, ok := oUserMap[id]; !ok {
|
||||
primary := syncer.getUserValue(user, key)
|
||||
if _, ok := myOUsers[primary]; !ok {
|
||||
newOUser := syncer.createOriginalUserFromUser(user)
|
||||
|
||||
fmt.Printf("New oUser: %v\n", newOUser)
|
||||
_, err = syncer.addUser(newOUser)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
fmt.Printf("New oUser: %v\n", newOUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (syncer *Syncer) syncUsersNoError() {
|
||||
err := syncer.syncUsers()
|
||||
if err != nil {
|
||||
fmt.Printf("syncUsersNoError() error: %s\n", err.Error())
|
||||
}
|
||||
}
|
||||
|
@ -31,8 +31,8 @@ type Credential struct {
|
||||
}
|
||||
|
||||
func (syncer *Syncer) getOriginalUsers() ([]*OriginalUser, error) {
|
||||
sql := fmt.Sprintf("select * from %s", syncer.getTable())
|
||||
results, err := syncer.Ormer.Engine.QueryString(sql)
|
||||
var results []map[string]string
|
||||
err := syncer.Ormer.Engine.Table(syncer.getTable()).Find(&results)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -64,19 +64,10 @@ func (syncer *Syncer) getOriginalUserMap() ([]*OriginalUser, map[string]*Origina
|
||||
|
||||
func (syncer *Syncer) addUser(user *OriginalUser) (bool, error) {
|
||||
m := syncer.getMapFromOriginalUser(user)
|
||||
keyString, valueString := syncer.getSqlKeyValueStringFromMap(m)
|
||||
|
||||
sql := fmt.Sprintf("insert into %s (%s) values (%s)", syncer.getTable(), keyString, valueString)
|
||||
res, err := syncer.Ormer.Engine.Exec(sql)
|
||||
affected, err := syncer.Ormer.Engine.Table(syncer.getTable()).Insert(m)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
affected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return affected != 0, nil
|
||||
}
|
||||
|
||||
@ -93,23 +84,14 @@ func (syncer *Syncer) getCasdoorColumns() []string {
|
||||
|
||||
func (syncer *Syncer) updateUser(user *OriginalUser) (bool, error) {
|
||||
key := syncer.getKey()
|
||||
|
||||
m := syncer.getMapFromOriginalUser(user)
|
||||
pkValue := m[key]
|
||||
delete(m, key)
|
||||
setString := syncer.getSqlSetStringFromMap(m)
|
||||
|
||||
sql := fmt.Sprintf("update %s set %s where %s = %s", syncer.getTable(), setString, key, pkValue)
|
||||
res, err := syncer.Ormer.Engine.Exec(sql)
|
||||
affected, err := syncer.Ormer.Engine.Table(syncer.getTable()).ID(pkValue).Update(&m)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
affected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return affected != 0, nil
|
||||
}
|
||||
|
||||
@ -185,7 +167,11 @@ func (syncer *Syncer) initAdapter() {
|
||||
if syncer.DatabaseType == "mssql" {
|
||||
dataSourceName = fmt.Sprintf("sqlserver://%s:%s@%s:%d?database=%s", syncer.User, syncer.Password, syncer.Host, syncer.Port, syncer.Database)
|
||||
} else if syncer.DatabaseType == "postgres" {
|
||||
dataSourceName = fmt.Sprintf("user=%s password=%s host=%s port=%d sslmode=disable dbname=%s", syncer.User, syncer.Password, syncer.Host, syncer.Port, syncer.Database)
|
||||
sslMode := "disable"
|
||||
if syncer.SslMode != "" {
|
||||
sslMode = syncer.SslMode
|
||||
}
|
||||
dataSourceName = fmt.Sprintf("user=%s password=%s host=%s port=%d sslmode=%s dbname=%s", syncer.User, syncer.Password, syncer.Host, syncer.Port, sslMode, syncer.Database)
|
||||
} else {
|
||||
dataSourceName = fmt.Sprintf("%s:%s@tcp(%s:%d)/", syncer.User, syncer.Password, syncer.Host, syncer.Port)
|
||||
}
|
||||
|
@ -322,19 +322,3 @@ func (syncer *Syncer) getSqlSetStringFromMap(m map[string]string) string {
|
||||
}
|
||||
return strings.Join(tokens, ", ")
|
||||
}
|
||||
|
||||
func (syncer *Syncer) getSqlKeyValueStringFromMap(m map[string]string) (string, string) {
|
||||
typeMap := syncer.getTableColumnsTypeMap()
|
||||
|
||||
keys := []string{}
|
||||
values := []string{}
|
||||
for k, v := range m {
|
||||
if typeMap[k] == "string" {
|
||||
v = fmt.Sprintf("'%s'", v)
|
||||
}
|
||||
|
||||
keys = append(keys, k)
|
||||
values = append(values, v)
|
||||
}
|
||||
return strings.Join(keys, ", "), strings.Join(values, ", ")
|
||||
}
|
||||
|
@ -185,38 +185,52 @@ func StoreCasTokenForProxyTicket(token *CasAuthenticationSuccess, targetService,
|
||||
}
|
||||
|
||||
func GenerateCasToken(userId string, service string) (string, error) {
|
||||
if user, err := GetUser(userId); err != nil {
|
||||
user, err := GetUser(userId)
|
||||
if err != nil {
|
||||
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]string{}
|
||||
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: v,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
st := fmt.Sprintf("ST-%d", rand.Int())
|
||||
stToServiceResponse.Store(st, &CasAuthenticationSuccessWrapper{
|
||||
AuthenticationSuccess: &authenticationSuccess,
|
||||
Service: service,
|
||||
UserId: userId,
|
||||
})
|
||||
return st, nil
|
||||
}
|
||||
|
||||
// GetValidationBySaml
|
||||
|
@ -194,16 +194,17 @@ type User struct {
|
||||
}
|
||||
|
||||
type Userinfo struct {
|
||||
Sub string `json:"sub"`
|
||||
Iss string `json:"iss"`
|
||||
Aud string `json:"aud"`
|
||||
Name string `json:"preferred_username,omitempty"`
|
||||
DisplayName string `json:"name,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
Avatar string `json:"picture,omitempty"`
|
||||
Address string `json:"address,omitempty"`
|
||||
Phone string `json:"phone,omitempty"`
|
||||
Groups []string `json:"groups,omitempty"`
|
||||
Sub string `json:"sub"`
|
||||
Iss string `json:"iss"`
|
||||
Aud string `json:"aud"`
|
||||
Name string `json:"preferred_username,omitempty"`
|
||||
DisplayName string `json:"name,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
EmailVerified bool `json:"email_verified,omitempty"`
|
||||
Avatar string `json:"picture,omitempty"`
|
||||
Address string `json:"address,omitempty"`
|
||||
Phone string `json:"phone,omitempty"`
|
||||
Groups []string `json:"groups,omitempty"`
|
||||
}
|
||||
|
||||
type ManagedAccount struct {
|
||||
@ -541,7 +542,7 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
|
||||
}
|
||||
}
|
||||
if isAdmin {
|
||||
columns = append(columns, "name", "email", "phone", "country_code")
|
||||
columns = append(columns, "name", "email", "phone", "country_code", "type")
|
||||
}
|
||||
|
||||
if util.ContainsString(columns, "groups") {
|
||||
@ -627,12 +628,10 @@ func AddUser(user *User) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if user.PasswordType == "" && organization.PasswordType != "" {
|
||||
user.PasswordType = organization.PasswordType
|
||||
if user.PasswordType == "" || user.PasswordType == "plain" {
|
||||
user.UpdateUserPassword(organization)
|
||||
}
|
||||
|
||||
user.UpdateUserPassword(organization)
|
||||
|
||||
err = user.UpdateUserHash()
|
||||
if err != nil {
|
||||
return false, err
|
||||
@ -759,6 +758,7 @@ func GetUserInfo(user *User, scope string, aud string, host string) *Userinfo {
|
||||
}
|
||||
if strings.Contains(scope, "email") {
|
||||
resp.Email = user.Email
|
||||
resp.EmailVerified = user.EmailVerified
|
||||
}
|
||||
if strings.Contains(scope, "address") {
|
||||
resp.Address = user.Location
|
||||
|
@ -1,6 +1,8 @@
|
||||
package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/casbin/casbin/v2"
|
||||
"github.com/casbin/casbin/v2/errors"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
@ -17,11 +19,28 @@ func NewUserGroupEnforcer(enforcer *casbin.Enforcer) *UserGroupEnforcer {
|
||||
}
|
||||
}
|
||||
|
||||
func (e *UserGroupEnforcer) checkModel() error {
|
||||
if _, ok := e.enforcer.GetModel()["g"]; !ok {
|
||||
return fmt.Errorf("The Casbin model used by enforcer doesn't support RBAC (\"[role_definition]\" section not found), please use a RBAC enabled Casbin model for the enforcer")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *UserGroupEnforcer) AddGroupForUser(user string, group string) (bool, error) {
|
||||
err := e.checkModel()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return e.enforcer.AddRoleForUser(user, GetGroupWithPrefix(group))
|
||||
}
|
||||
|
||||
func (e *UserGroupEnforcer) AddGroupsForUser(user string, groups []string) (bool, error) {
|
||||
err := e.checkModel()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
g := make([]string, len(groups))
|
||||
for i, group := range groups {
|
||||
g[i] = GetGroupWithPrefix(group)
|
||||
@ -30,14 +49,29 @@ func (e *UserGroupEnforcer) AddGroupsForUser(user string, groups []string) (bool
|
||||
}
|
||||
|
||||
func (e *UserGroupEnforcer) DeleteGroupForUser(user string, group string) (bool, error) {
|
||||
err := e.checkModel()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return e.enforcer.DeleteRoleForUser(user, GetGroupWithPrefix(group))
|
||||
}
|
||||
|
||||
func (e *UserGroupEnforcer) DeleteGroupsForUser(user string) (bool, error) {
|
||||
err := e.checkModel()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return e.enforcer.DeleteRolesForUser(user)
|
||||
}
|
||||
|
||||
func (e *UserGroupEnforcer) GetGroupsForUser(user string) ([]string, error) {
|
||||
err := e.checkModel()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
groups, err := e.enforcer.GetRolesForUser(user)
|
||||
for i, group := range groups {
|
||||
groups[i] = GetGroupWithoutPrefix(group)
|
||||
@ -46,6 +80,11 @@ func (e *UserGroupEnforcer) GetGroupsForUser(user string) ([]string, error) {
|
||||
}
|
||||
|
||||
func (e *UserGroupEnforcer) GetAllUsersByGroup(group string) ([]string, error) {
|
||||
err := e.checkModel()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
users, err := e.enforcer.GetUsersForRole(GetGroupWithPrefix(group))
|
||||
if err != nil {
|
||||
if err == errors.ERR_NAME_NOT_FOUND {
|
||||
@ -65,13 +104,17 @@ func GetGroupWithoutPrefix(group string) string {
|
||||
}
|
||||
|
||||
func (e *UserGroupEnforcer) GetUserNamesByGroupName(groupName string) ([]string, error) {
|
||||
var names []string
|
||||
err := e.checkModel()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userIds, err := e.GetAllUsersByGroup(groupName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
names := []string{}
|
||||
for _, userId := range userIds {
|
||||
_, name := util.GetOwnerAndNameFromIdNoCheck(userId)
|
||||
names = append(names, name)
|
||||
@ -81,7 +124,12 @@ func (e *UserGroupEnforcer) GetUserNamesByGroupName(groupName string) ([]string,
|
||||
}
|
||||
|
||||
func (e *UserGroupEnforcer) UpdateGroupsForUser(user string, groups []string) (bool, error) {
|
||||
_, err := e.DeleteGroupsForUser(user)
|
||||
err := e.checkModel()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
_, err = e.DeleteGroupsForUser(user)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
@ -73,8 +73,8 @@ func parseListItem(lines *[]string, i int) []string {
|
||||
return trimmedItems
|
||||
}
|
||||
|
||||
func UploadUsers(owner string, fileId string) (bool, error) {
|
||||
table := xlsx.ReadXlsxFile(fileId)
|
||||
func UploadUsers(owner string, path string) (bool, error) {
|
||||
table := xlsx.ReadXlsxFile(path)
|
||||
|
||||
oldUserMap, err := getUserMap(owner)
|
||||
if err != nil {
|
||||
|
@ -14,10 +14,7 @@
|
||||
|
||||
package pp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
import "net/http"
|
||||
|
||||
type DummyPaymentProvider struct{}
|
||||
|
||||
@ -27,8 +24,7 @@ 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) {
|
||||
payUrl := fmt.Sprintf("/payments/%s/result", paymentName)
|
||||
return payUrl, "", nil
|
||||
return returnUrl, "", nil
|
||||
}
|
||||
|
||||
func (pp *DummyPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) {
|
||||
|
@ -114,8 +114,8 @@ func (pp *PaypalPaymentProvider) Notify(request *http.Request, body []byte, auth
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if captureRsp.Code != paypal.Success {
|
||||
errDetail := captureRsp.ErrorResponse.Details[0]
|
||||
if detailRsp.Code != paypal.Success {
|
||||
errDetail := detailRsp.ErrorResponse.Details[0]
|
||||
switch errDetail.Issue {
|
||||
case "ORDER_NOT_APPROVED":
|
||||
notifyResult.PaymentStatus = PaymentStateCanceled
|
||||
|
@ -14,9 +14,7 @@
|
||||
|
||||
package pp
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
import "net/http"
|
||||
|
||||
type PaymentState string
|
||||
|
||||
|
@ -17,6 +17,7 @@ package pp
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
@ -31,17 +32,22 @@ type WechatPayNotifyResponse struct {
|
||||
|
||||
type WechatPaymentProvider struct {
|
||||
Client *wechat.ClientV3
|
||||
appId string
|
||||
AppId string
|
||||
}
|
||||
|
||||
func NewWechatPaymentProvider(mchId string, apiV3Key string, appId string, mchCertSerialNumber string, privateKey string) (*WechatPaymentProvider, error) {
|
||||
if appId == "" && mchId == "" && mchCertSerialNumber == "" && apiV3Key == "" && privateKey == "" {
|
||||
func NewWechatPaymentProvider(mchId string, apiV3Key string, appId string, serialNo string, privateKey string) (*WechatPaymentProvider, error) {
|
||||
// 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
|
||||
}
|
||||
|
||||
pp := &WechatPaymentProvider{appId: appId}
|
||||
|
||||
clientV3, err := wechat.NewClientV3(mchId, mchCertSerialNumber, apiV3Key, privateKey)
|
||||
clientV3, err := wechat.NewClientV3(mchId, serialNo, apiV3Key, privateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -50,73 +56,70 @@ func NewWechatPaymentProvider(mchId string, apiV3Key string, appId string, mchCe
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pp.Client = clientV3.SetPlatformCert([]byte(platformCert), serialNo)
|
||||
pp := &WechatPaymentProvider{
|
||||
Client: clientV3.SetPlatformCert([]byte(platformCert), serialNo),
|
||||
AppId: appId,
|
||||
}
|
||||
|
||||
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) {
|
||||
// pp.Client.DebugSwitch = gopay.DebugOn
|
||||
|
||||
bm := gopay.BodyMap{}
|
||||
|
||||
bm.Set("attach", joinAttachString([]string{productDisplayName, productName, providerName}))
|
||||
bm.Set("appid", pp.appId)
|
||||
bm.Set("appid", pp.AppId)
|
||||
bm.Set("description", productDisplayName)
|
||||
bm.Set("notify_url", notifyUrl)
|
||||
bm.Set("out_trade_no", paymentName)
|
||||
bm.SetBodyMap("amount", func(bm gopay.BodyMap) {
|
||||
bm.Set("total", int(price*100))
|
||||
bm.Set("currency", "CNY")
|
||||
bm.Set("total", priceFloat64ToInt64(price))
|
||||
bm.Set("currency", currency)
|
||||
})
|
||||
|
||||
wxRsp, err := pp.Client.V3TransactionNative(context.Background(), bm)
|
||||
nativeRsp, err := pp.Client.V3TransactionNative(context.Background(), bm)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if wxRsp.Code != wechat.Success {
|
||||
return "", "", errors.New(wxRsp.Error)
|
||||
if nativeRsp.Code != wechat.Success {
|
||||
return "", "", errors.New(nativeRsp.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) {
|
||||
notifyReq, err := wechat.V3ParseNotify(request)
|
||||
notifyResult := &NotifyResult{}
|
||||
queryRsp, err := pp.Client.V3TransactionQueryOrder(context.Background(), wechat.OutTradeNo, orderId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cert := pp.Client.WxPublicKey()
|
||||
err = notifyReq.VerifySignByPK(cert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if queryRsp.Code != wechat.Success {
|
||||
return nil, errors.New(queryRsp.Error)
|
||||
}
|
||||
|
||||
apiKey := string(pp.Client.ApiV3Key)
|
||||
result, err := notifyReq.DecryptCipherText(apiKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
switch queryRsp.Response.TradeState {
|
||||
case "SUCCESS":
|
||||
// skip
|
||||
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
|
||||
}
|
||||
|
||||
paymentName := result.OutTradeNo
|
||||
price := float64(result.Amount.PayerTotal) / 100
|
||||
|
||||
productDisplayName, productName, providerName, err := parseAttachString(result.Attach)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
notifyResult := &NotifyResult{
|
||||
productDisplayName, productName, providerName, _ := parseAttachString(queryRsp.Response.Attach)
|
||||
notifyResult = &NotifyResult{
|
||||
ProductName: productName,
|
||||
ProductDisplayName: productDisplayName,
|
||||
ProviderName: providerName,
|
||||
OrderId: orderId,
|
||||
Price: price,
|
||||
Price: priceInt64ToFloat64(int64(queryRsp.Response.Amount.Total)),
|
||||
PaymentStatus: PaymentStatePaid,
|
||||
PaymentName: paymentName,
|
||||
PaymentName: queryRsp.Response.OutTradeNo,
|
||||
}
|
||||
return notifyResult, nil
|
||||
}
|
||||
|
@ -29,7 +29,13 @@ func AutoSigninFilter(ctx *context.Context) {
|
||||
|
||||
// GET parameter like "/page?access_token=123" or
|
||||
// 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 != "" {
|
||||
token, err := object.GetTokenByAccessToken(accessToken)
|
||||
|
@ -273,7 +273,7 @@ func initAPI() {
|
||||
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/p3/serviceValidate", &controllers.RootController{}, "GET:CasP3ServiceAndProxyValidate")
|
||||
beego.Router("/cas/:organization/:application/p3/proxyValidate", &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:CasP3ProxyValidate")
|
||||
beego.Router("/cas/:organization/:application/samlValidate", &controllers.RootController{}, "POST:SamlValidate")
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ package routers
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
@ -59,6 +60,13 @@ func StaticFilter(ctx *context.Context) {
|
||||
path = "web/build/index.html"
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@ -23,76 +24,69 @@ import (
|
||||
"github.com/casdoor/oss"
|
||||
)
|
||||
|
||||
var baseFolder = "files"
|
||||
|
||||
// FileSystem file system storage
|
||||
type FileSystem struct {
|
||||
Base string
|
||||
// LocalFileSystemProvider file system storage
|
||||
type LocalFileSystemProvider struct {
|
||||
BaseDir string
|
||||
}
|
||||
|
||||
// NewFileSystem initialize the local file system storage
|
||||
func NewFileSystem(base string) *FileSystem {
|
||||
absBase, err := filepath.Abs(base)
|
||||
// NewLocalFileSystemStorageProvider initialize the local file system storage
|
||||
func NewLocalFileSystemStorageProvider() *LocalFileSystemProvider {
|
||||
baseFolder := "files"
|
||||
absBase, err := filepath.Abs(baseFolder)
|
||||
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
|
||||
func (fileSystem FileSystem) GetFullPath(path string) string {
|
||||
func (sp LocalFileSystemProvider) GetFullPath(path string) string {
|
||||
fullPath := path
|
||||
if !strings.HasPrefix(path, fileSystem.Base) {
|
||||
fullPath, _ = filepath.Abs(filepath.Join(fileSystem.Base, path))
|
||||
if !strings.HasPrefix(path, sp.BaseDir) {
|
||||
fullPath, _ = filepath.Abs(filepath.Join(sp.BaseDir, path))
|
||||
}
|
||||
return fullPath
|
||||
}
|
||||
|
||||
// Get receive file with given path
|
||||
func (fileSystem FileSystem) Get(path string) (*os.File, error) {
|
||||
return os.Open(fileSystem.GetFullPath(path))
|
||||
func (sp LocalFileSystemProvider) Get(path string) (*os.File, error) {
|
||||
return os.Open(sp.GetFullPath(path))
|
||||
}
|
||||
|
||||
// GetStream get file as stream
|
||||
func (fileSystem FileSystem) GetStream(path string) (io.ReadCloser, error) {
|
||||
return os.Open(fileSystem.GetFullPath(path))
|
||||
func (sp LocalFileSystemProvider) GetStream(path string) (io.ReadCloser, error) {
|
||||
return os.Open(sp.GetFullPath(path))
|
||||
}
|
||||
|
||||
// Put store a reader into given path
|
||||
func (fileSystem FileSystem) Put(path string, reader io.Reader) (*oss.Object, error) {
|
||||
var (
|
||||
fullPath = fileSystem.GetFullPath(path)
|
||||
err = os.MkdirAll(filepath.Dir(fullPath), os.ModePerm)
|
||||
)
|
||||
func (sp LocalFileSystemProvider) Put(path string, reader io.Reader) (*oss.Object, error) {
|
||||
fullPath := sp.GetFullPath(path)
|
||||
|
||||
err := os.MkdirAll(filepath.Dir(fullPath), os.ModePerm)
|
||||
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))
|
||||
|
||||
if err == nil {
|
||||
if seeker, ok := reader.(io.ReadSeeker); ok {
|
||||
seeker.Seek(0, 0)
|
||||
}
|
||||
_, err = io.Copy(dst, reader)
|
||||
}
|
||||
|
||||
return &oss.Object{Path: path, Name: filepath.Base(path), StorageInterface: fileSystem}, err
|
||||
return &oss.Object{Path: path, Name: filepath.Base(path), StorageInterface: sp}, err
|
||||
}
|
||||
|
||||
// Delete delete file
|
||||
func (fileSystem FileSystem) Delete(path string) error {
|
||||
return os.Remove(fileSystem.GetFullPath(path))
|
||||
func (sp LocalFileSystemProvider) Delete(path string) error {
|
||||
return os.Remove(sp.GetFullPath(path))
|
||||
}
|
||||
|
||||
// List list all objects under current path
|
||||
func (fileSystem FileSystem) List(path string) ([]*oss.Object, error) {
|
||||
var (
|
||||
objects []*oss.Object
|
||||
fullPath = fileSystem.GetFullPath(path)
|
||||
)
|
||||
func (sp LocalFileSystemProvider) List(path string) ([]*oss.Object, error) {
|
||||
objects := []*oss.Object{}
|
||||
fullPath := sp.GetFullPath(path)
|
||||
|
||||
filepath.Walk(fullPath, func(path string, info os.FileInfo, err error) error {
|
||||
if path == fullPath {
|
||||
@ -102,10 +96,10 @@ func (fileSystem FileSystem) List(path string) ([]*oss.Object, error) {
|
||||
if err == nil && !info.IsDir() {
|
||||
modTime := info.ModTime()
|
||||
objects = append(objects, &oss.Object{
|
||||
Path: strings.TrimPrefix(path, fileSystem.Base),
|
||||
Path: strings.TrimPrefix(path, sp.BaseDir),
|
||||
Name: info.Name(),
|
||||
LastModified: &modTime,
|
||||
StorageInterface: fileSystem,
|
||||
StorageInterface: sp,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
@ -114,16 +108,12 @@ func (fileSystem FileSystem) List(path string) ([]*oss.Object, error) {
|
||||
return objects, nil
|
||||
}
|
||||
|
||||
// GetEndpoint get endpoint, FileSystem's endpoint is /
|
||||
func (fileSystem FileSystem) GetEndpoint() string {
|
||||
// GetEndpoint get endpoint, LocalFileSystemProvider's endpoint is /
|
||||
func (sp LocalFileSystemProvider) GetEndpoint() string {
|
||||
return "/"
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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 {
|
||||
switch providerType {
|
||||
case "Local File System":
|
||||
return NewLocalFileSystemStorageProvider(clientId, clientSecret, region, bucket, endpoint)
|
||||
return NewLocalFileSystemStorageProvider()
|
||||
case "AWS S3":
|
||||
return NewAwsS3StorageProvider(clientId, clientSecret, region, bucket, endpoint)
|
||||
case "MinIO":
|
||||
|
10
util/path.go
10
util/path.go
@ -34,16 +34,6 @@ func GetPath(path string) string {
|
||||
return filepath.Dir(path)
|
||||
}
|
||||
|
||||
func EnsureFileFolderExists(path string) {
|
||||
p := GetPath(path)
|
||||
if !FileExist(p) {
|
||||
err := os.MkdirAll(p, os.ModePerm)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ListFiles(path string) []string {
|
||||
res := []string{}
|
||||
|
||||
|
@ -14,8 +14,13 @@
|
||||
|
||||
package util
|
||||
|
||||
import "fmt"
|
||||
import "io/ioutil"
|
||||
|
||||
func GetUploadXlsxPath(fileId string) string {
|
||||
return fmt.Sprintf("tmpFiles/%s.xlsx", fileId)
|
||||
file, err := ioutil.TempFile("", fileId)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return file.Name()
|
||||
}
|
||||
|
@ -1,40 +0,0 @@
|
||||
// Copyright 2021 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 util
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetUploadXlsxPath(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
description string
|
||||
input string
|
||||
expected interface{}
|
||||
}{
|
||||
{"scenery one", "casdoor", "tmpFiles/casdoor.xlsx"},
|
||||
{"scenery two", "casbin", "tmpFiles/casbin.xlsx"},
|
||||
{"scenery three", "loremIpsum", "tmpFiles/loremIpsum.xlsx"},
|
||||
{"scenery four", "", "tmpFiles/.xlsx"},
|
||||
}
|
||||
for _, scenery := range scenarios {
|
||||
t.Run(scenery.description, func(t *testing.T) {
|
||||
actual := GetUploadXlsxPath(scenery.input)
|
||||
assert.Equal(t, scenery.expected, actual, "The returned value not is expected")
|
||||
})
|
||||
}
|
||||
}
|
@ -23,6 +23,7 @@ import (
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@ -186,32 +187,6 @@ func IsStringsEmpty(strs ...string) bool {
|
||||
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 {
|
||||
data, err := os.ReadFile(filepath.Clean(path))
|
||||
if err != nil {
|
||||
@ -315,3 +290,13 @@ func ParseIdToString(input interface{}) (string, error) {
|
||||
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) {
|
||||
scenarios := []struct {
|
||||
description string
|
||||
|
@ -34,6 +34,7 @@
|
||||
"i18next": "^19.8.9",
|
||||
"libphonenumber-js": "^1.10.19",
|
||||
"moment": "^2.29.1",
|
||||
"qrcode.react": "^3.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-app-polyfill": "^3.0.0",
|
||||
"react-codemirror2": "^7.2.1",
|
||||
@ -52,7 +53,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"start": "cross-env PORT=7001 craco start",
|
||||
"build": "craco --max_old_space_size=4096 build",
|
||||
"build": "craco build",
|
||||
"test": "craco test",
|
||||
"eject": "craco eject",
|
||||
"crowdin:sync": "crowdin upload && crowdin download",
|
||||
@ -82,6 +83,9 @@
|
||||
"@babel/eslint-parser": "^7.18.9",
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||
"@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",
|
||||
"cypress": "^12.5.1",
|
||||
"eslint": "8.22.0",
|
||||
@ -91,10 +95,7 @@
|
||||
"lint-staged": "^13.0.3",
|
||||
"stylelint": "^14.11.0",
|
||||
"stylelint-config-recommended-less": "^1.0.4",
|
||||
"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"
|
||||
"stylelint-config-standard": "^28.0.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
"src/**/*.{css,less}": [
|
||||
|
@ -89,6 +89,7 @@ import ThemeSelect from "./common/select/ThemeSelect";
|
||||
import OrganizationSelect from "./common/select/OrganizationSelect";
|
||||
import {clearWeb3AuthToken} from "./auth/Web3Auth";
|
||||
import AccountAvatar from "./account/AccountAvatar";
|
||||
import OpenTour from "./common/OpenTour";
|
||||
|
||||
const {Header, Footer, Content} = Layout;
|
||||
|
||||
@ -379,6 +380,7 @@ class App extends Component {
|
||||
});
|
||||
}} />
|
||||
<LanguageSelect languages={this.state.account.organization.languages} />
|
||||
<OpenTour />
|
||||
{Setting.isAdminUser(this.state.account) && !Setting.isMobile() &&
|
||||
<OrganizationSelect
|
||||
initValue={Setting.getOrganization()}
|
||||
@ -448,7 +450,7 @@ class App extends Component {
|
||||
|
||||
res.push(Setting.getItem(<Link style={{color: "black"}} to="/sessions">{i18next.t("general:Logging & Auditing")}</Link>, "/logs", <WalletTwoTone />, [
|
||||
Setting.getItem(<Link to="/sessions">{i18next.t("general:Sessions")}</Link>, "/sessions"),
|
||||
Setting.getItem(<a target="_blank" rel="noreferrer" href={"http://localhost:18001/records"}>{i18next.t("general:Records")}</a>, "/records"),
|
||||
Setting.getItem(<a target="_blank" rel="noreferrer" href={Conf.CasvisorUrl}>{i18next.t("general:Records")}</a>, "/records"),
|
||||
Setting.getItem(<Link to="/tokens">{i18next.t("general:Tokens")}</Link>, "/tokens"),
|
||||
]));
|
||||
|
||||
@ -460,11 +462,17 @@ class App extends Component {
|
||||
Setting.getItem(<Link to="/subscriptions">{i18next.t("general:Subscriptions")}</Link>, "/subscriptions"),
|
||||
]));
|
||||
|
||||
res.push(Setting.getItem(<Link style={{color: "black"}} to="/sysinfo">{i18next.t("general:Admin")}</Link>, "/admin", <SettingTwoTone />, [
|
||||
Setting.getItem(<Link to="/sysinfo">{i18next.t("general:System Info")}</Link>, "/sysinfo"),
|
||||
Setting.getItem(<Link to="/syncers">{i18next.t("general:Syncers")}</Link>, "/syncers"),
|
||||
Setting.getItem(<Link to="/webhooks">{i18next.t("general:Webhooks")}</Link>, "/webhooks"),
|
||||
Setting.getItem(<a target="_blank" rel="noreferrer" href={Setting.isLocalhost() ? `${Setting.ServerUrl}/swagger` : "/swagger"}>{i18next.t("general:Swagger")}</a>, "/swagger")]));
|
||||
if (Setting.isAdminUser(this.state.account)) {
|
||||
res.push(Setting.getItem(<Link style={{color: "black"}} to="/sysinfo">{i18next.t("general:Admin")}</Link>, "/admin", <SettingTwoTone />, [
|
||||
Setting.getItem(<Link to="/sysinfo">{i18next.t("general:System Info")}</Link>, "/sysinfo"),
|
||||
Setting.getItem(<Link to="/syncers">{i18next.t("general:Syncers")}</Link>, "/syncers"),
|
||||
Setting.getItem(<Link to="/webhooks">{i18next.t("general:Webhooks")}</Link>, "/webhooks"),
|
||||
Setting.getItem(<a target="_blank" rel="noreferrer" href={Setting.isLocalhost() ? `${Setting.ServerUrl}/swagger` : "/swagger"}>{i18next.t("general:Swagger")}</a>, "/swagger")]));
|
||||
} else {
|
||||
res.push(Setting.getItem(<Link style={{color: "black"}} to="/syncers">{i18next.t("general:Admin")}</Link>, "/admin", <SettingTwoTone />, [
|
||||
Setting.getItem(<Link to="/syncers">{i18next.t("general:Syncers")}</Link>, "/syncers"),
|
||||
Setting.getItem(<Link to="/webhooks">{i18next.t("general:Webhooks")}</Link>, "/webhooks")]));
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
@ -563,9 +571,7 @@ class App extends Component {
|
||||
|
||||
renderContent() {
|
||||
const onClick = ({key}) => {
|
||||
if (key === "/swagger") {
|
||||
window.open(Setting.isLocalhost() ? `${Setting.ServerUrl}/swagger` : "/swagger", "_blank");
|
||||
} else {
|
||||
if (key !== "/swagger" && key !== "/records") {
|
||||
if (this.state.requiredEnableMfa) {
|
||||
Setting.showMessage("info", "Please enable MFA first!");
|
||||
} else {
|
||||
@ -657,7 +663,9 @@ class App extends Component {
|
||||
window.location.pathname.startsWith("/result") ||
|
||||
window.location.pathname.startsWith("/cas") ||
|
||||
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("/qrcode") ;
|
||||
}
|
||||
|
||||
renderPage() {
|
||||
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import {Button, Card, Col, ConfigProvider, Input, Popover, Radio, Result, Row, Select, Switch, Upload} from "antd";
|
||||
import {Button, Card, Col, ConfigProvider, Input, List, Popover, Radio, Result, Row, Select, Space, Switch, Upload} from "antd";
|
||||
import {CopyOutlined, LinkOutlined, UploadOutlined} from "@ant-design/icons";
|
||||
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
||||
import * as CertBackend from "./backend/CertBackend";
|
||||
@ -138,6 +138,10 @@ class ApplicationEditPage extends React.Component {
|
||||
application.tags = [];
|
||||
}
|
||||
|
||||
if (application.invitationCodes === null) {
|
||||
application.invitationCodes = [];
|
||||
}
|
||||
|
||||
this.setState({
|
||||
application: application,
|
||||
});
|
||||
@ -201,7 +205,6 @@ class ApplicationEditPage extends React.Component {
|
||||
|
||||
updateApplicationField(key, value) {
|
||||
value = this.parseApplicationField(key, value);
|
||||
|
||||
const application = this.state.application;
|
||||
application[key] = value;
|
||||
this.setState({
|
||||
@ -539,7 +542,7 @@ class ApplicationEditPage extends React.Component {
|
||||
{Setting.getLabel(i18next.t("signup:Terms of Use"), i18next.t("signup:Terms of Use - Tooltip"))} :
|
||||
</Col>
|
||||
<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);
|
||||
}} />
|
||||
<Upload maxCount={1} accept=".html" showUploadList={false}
|
||||
@ -813,18 +816,68 @@ class ApplicationEditPage extends React.Component {
|
||||
</Row>
|
||||
{
|
||||
!this.state.application.enableSignUp ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("application:Signup items"), i18next.t("application:Signup items - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<SignupTable
|
||||
title={i18next.t("application:Signup items")}
|
||||
table={this.state.application.signupItems}
|
||||
onUpdateTable={(value) => {this.updateApplicationField("signupItems", value);}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<React.Fragment>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("application:Signup items"), i18next.t("application:Signup items - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<SignupTable
|
||||
title={i18next.t("application:Signup items")}
|
||||
table={this.state.application.signupItems}
|
||||
onUpdateTable={(value) => {
|
||||
this.updateApplicationField("signupItems", value);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("application:Invitation code"), i18next.t("application:Invitation code - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<List
|
||||
header={
|
||||
<Button type="primary" onClick={() => {
|
||||
this.updateApplicationField("invitationCodes", Setting.addRow(this.state.application.invitationCodes, Setting.getRandomName()));
|
||||
}
|
||||
}>
|
||||
{i18next.t("general:Add")}
|
||||
</Button>
|
||||
}
|
||||
dataSource={this.state.application.invitationCodes.map(code => {
|
||||
return {code: code};
|
||||
})}
|
||||
renderItem={(item, index) => (
|
||||
<List.Item key={index}>
|
||||
<Space>
|
||||
<Input value={item.code} onChange={e => {
|
||||
const invitationCodes = [...this.state.application.invitationCodes];
|
||||
invitationCodes[index] = e.target.value;
|
||||
this.updateApplicationField("invitationCodes", invitationCodes);
|
||||
}} />
|
||||
</Space>
|
||||
<Space>
|
||||
<Button icon={<CopyOutlined />} onClick={() => {
|
||||
copy(item.code);
|
||||
Setting.showMessage("success", i18next.t("application:Invitation code copied to clipboard successfully"));
|
||||
}
|
||||
}>
|
||||
{i18next.t("general:Copy")}
|
||||
</Button>
|
||||
<Button type="primary" danger onClick={() => {
|
||||
this.updateApplicationField("invitationCodes", this.state.application.invitationCodes.filter(code => code !== item.code));
|
||||
}
|
||||
}>
|
||||
{i18next.t("general:Delete")}
|
||||
</Button>
|
||||
</Space>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
|
@ -13,11 +13,12 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import {Button, Input, Result, Space} from "antd";
|
||||
import {Button, Input, Result, Space, Tour} from "antd";
|
||||
import {SearchOutlined} from "@ant-design/icons";
|
||||
import Highlighter from "react-highlight-words";
|
||||
import i18next from "i18next";
|
||||
import * as Setting from "./Setting";
|
||||
import * as TourConfig from "./TourConfig";
|
||||
|
||||
class BaseListPage extends React.Component {
|
||||
constructor(props) {
|
||||
@ -33,6 +34,7 @@ class BaseListPage extends React.Component {
|
||||
searchText: "",
|
||||
searchedColumn: "",
|
||||
isAuthorized: true,
|
||||
isTourVisible: TourConfig.getTourVisible(),
|
||||
};
|
||||
}
|
||||
|
||||
@ -41,14 +43,23 @@ class BaseListPage extends React.Component {
|
||||
this.fetch({pagination});
|
||||
};
|
||||
|
||||
handleTourChange = () => {
|
||||
this.setState({isTourVisible: TourConfig.getTourVisible()});
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
window.addEventListener("storageOrganizationChanged", this.handleOrganizationChange);
|
||||
window.addEventListener("storageTourChanged", this.handleTourChange);
|
||||
if (!Setting.isAdminUser(this.props.account)) {
|
||||
Setting.setOrganization("All");
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.state.intervalId !== null) {
|
||||
clearInterval(this.state.intervalId);
|
||||
}
|
||||
window.removeEventListener("storageTourChanged", this.handleTourChange);
|
||||
window.removeEventListener("storageOrganizationChanged", this.handleOrganizationChange);
|
||||
}
|
||||
|
||||
@ -144,6 +155,37 @@ class BaseListPage extends React.Component {
|
||||
});
|
||||
};
|
||||
|
||||
setIsTourVisible = () => {
|
||||
TourConfig.setIsTourVisible(false);
|
||||
this.setState({isTourVisible: false});
|
||||
};
|
||||
|
||||
getSteps = () => {
|
||||
const nextPathName = TourConfig.getNextUrl();
|
||||
const steps = TourConfig.getSteps();
|
||||
steps.map((item, index) => {
|
||||
if (!index) {
|
||||
item.target = () => document.querySelector("table");
|
||||
} else {
|
||||
item.target = () => document.getElementById(item.id) || null;
|
||||
}
|
||||
if (index === steps.length - 1) {
|
||||
item.nextButtonProps = {
|
||||
children: TourConfig.getNextButtonChild(nextPathName),
|
||||
};
|
||||
}
|
||||
});
|
||||
return steps;
|
||||
};
|
||||
|
||||
handleTourComplete = () => {
|
||||
const nextPathName = TourConfig.getNextUrl();
|
||||
if (nextPathName !== "") {
|
||||
this.props.history.push("/" + nextPathName);
|
||||
TourConfig.setIsTourVisible(true);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
if (!this.state.isAuthorized) {
|
||||
return (
|
||||
@ -161,6 +203,17 @@ class BaseListPage extends React.Component {
|
||||
{
|
||||
this.renderTable(this.state.data)
|
||||
}
|
||||
<Tour
|
||||
open={Setting.isMobile() ? false : this.state.isTourVisible}
|
||||
onClose={this.setIsTourVisible}
|
||||
steps={this.getSteps()}
|
||||
indicatorsRender={(current, total) => (
|
||||
<span>
|
||||
{current + 1} / {total}
|
||||
</span>
|
||||
)}
|
||||
onFinish={this.handleTourComplete}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -14,6 +14,8 @@
|
||||
|
||||
export const DefaultApplication = "app-built-in";
|
||||
|
||||
export const CasvisorUrl = "https://github.com/casbin/casvisor";
|
||||
|
||||
export const ShowGithubCorner = false;
|
||||
export const IsDemoMode = false;
|
||||
|
||||
|
@ -29,6 +29,9 @@ import PromptPage from "./auth/PromptPage";
|
||||
import ResultPage from "./auth/ResultPage";
|
||||
import CasLogout from "./auth/CasLogout";
|
||||
import {authConfig} from "./auth/Auth";
|
||||
import ProductBuyPage from "./ProductBuyPage";
|
||||
import PaymentResultPage from "./PaymentResultPage";
|
||||
import QrCodePage from "./QrCodePage";
|
||||
|
||||
class EntryPage extends React.Component {
|
||||
constructor(props) {
|
||||
@ -108,7 +111,10 @@ class EntryPage extends React.Component {
|
||||
<Route exact path="/result/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/cas/:owner/:casApplicationName/logout" render={(props) => this.renderHomeIfLoggedIn(<CasLogout {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/cas/:owner/:casApplicationName/login" render={(props) => {return (<LoginPage {...this.props} application={this.state.application} type={"cas"} mode={"signin"} onUpdateApplication={onUpdateApplication} {...props} />);}} />
|
||||
<Route exact path="/select-plan/:owner/:pricingName" render={(props) => this.renderHomeIfLoggedIn(<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/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>
|
||||
</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"),
|
||||
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"),
|
||||
dataIndex: "user",
|
||||
@ -158,14 +158,6 @@ class PaymentListPage extends BaseListPage {
|
||||
return Setting.getFormattedDate(text);
|
||||
},
|
||||
},
|
||||
// {
|
||||
// title: i18next.t("general:Display name"),
|
||||
// dataIndex: 'displayName',
|
||||
// key: 'displayName',
|
||||
// width: '160px',
|
||||
// sorter: true,
|
||||
// ...this.getColumnSearchProps('displayName'),
|
||||
// },
|
||||
{
|
||||
title: i18next.t("provider:Type"),
|
||||
dataIndex: "type",
|
||||
@ -187,6 +179,13 @@ class PaymentListPage extends BaseListPage {
|
||||
// width: '160px',
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("productDisplayName"),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/products/${record.owner}/${record.productName}`}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("product:Price"),
|
||||
@ -265,7 +264,7 @@ class PaymentListPage extends BaseListPage {
|
||||
value = params.type;
|
||||
}
|
||||
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) => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
|
@ -15,17 +15,24 @@
|
||||
import React from "react";
|
||||
import {Button, Result, Spin} from "antd";
|
||||
import * as PaymentBackend from "./backend/PaymentBackend";
|
||||
import * as PricingBackend from "./backend/PricingBackend";
|
||||
import * as SubscriptionBackend from "./backend/SubscriptionBackend";
|
||||
import * as Setting from "./Setting";
|
||||
import i18next from "i18next";
|
||||
|
||||
class PaymentResultPage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
this.state = {
|
||||
classes: props,
|
||||
paymentName: props.match.params.paymentName,
|
||||
organizationName: props.match.params.organizationName,
|
||||
owner: props.match?.params?.organizationName ?? props.match?.params?.owner ?? null,
|
||||
paymentName: props.match?.params?.paymentName ?? null,
|
||||
pricingName: props.pricingName ?? props.match?.params?.pricingName ?? null,
|
||||
subscriptionName: params.get("subscription"),
|
||||
payment: null,
|
||||
pricing: props.pricing ?? null,
|
||||
subscription: props.subscription ?? null,
|
||||
timeout: null,
|
||||
};
|
||||
}
|
||||
@ -40,28 +47,77 @@ class PaymentResultPage extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
getPayment() {
|
||||
PaymentBackend.getPayment(this.state.organizationName, this.state.paymentName)
|
||||
.then((res) => {
|
||||
this.setState({
|
||||
payment: res.data,
|
||||
});
|
||||
// window.console.log("payment=", res.data);
|
||||
if (res.data.state === "Created") {
|
||||
if (["PayPal", "Stripe"].includes(res.data.type)) {
|
||||
this.setState({
|
||||
timeout: setTimeout(() => {
|
||||
PaymentBackend.notifyPayment(this.state.organizationName, this.state.paymentName)
|
||||
.then((res) => {
|
||||
this.getPayment();
|
||||
});
|
||||
}, 1000),
|
||||
});
|
||||
} else {
|
||||
this.setState({timeout: setTimeout(() => this.getPayment(), 1000)});
|
||||
}
|
||||
}
|
||||
setStateAsync(state) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.setState(state, () => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
onUpdatePricing(pricing) {
|
||||
this.props.onUpdatePricing(pricing);
|
||||
}
|
||||
|
||||
async getPayment() {
|
||||
if (!(this.state.owner && (this.state.paymentName || (this.state.pricingName && this.state.subscriptionName)))) {
|
||||
return ;
|
||||
}
|
||||
try {
|
||||
// loading price & subscription
|
||||
if (this.state.pricingName && this.state.subscriptionName) {
|
||||
if (!this.state.pricing) {
|
||||
const res = await PricingBackend.getPricing(this.state.owner, this.state.pricingName);
|
||||
if (res.status !== "ok") {
|
||||
throw new Error(res.msg);
|
||||
}
|
||||
const pricing = res.data;
|
||||
await this.setStateAsync({
|
||||
pricing: pricing,
|
||||
});
|
||||
}
|
||||
if (!this.state.subscription) {
|
||||
const res = await SubscriptionBackend.getSubscription(this.state.owner, this.state.subscriptionName);
|
||||
if (res.status !== "ok") {
|
||||
throw new Error(res.msg);
|
||||
}
|
||||
const subscription = res.data;
|
||||
await this.setStateAsync({
|
||||
subscription: subscription,
|
||||
});
|
||||
}
|
||||
const paymentName = this.state.subscription.payment;
|
||||
await this.setStateAsync({
|
||||
paymentName: paymentName,
|
||||
});
|
||||
this.onUpdatePricing(this.state.pricing);
|
||||
}
|
||||
const res = await PaymentBackend.getPayment(this.state.owner, this.state.paymentName);
|
||||
if (res.status !== "ok") {
|
||||
throw new Error(res.msg);
|
||||
}
|
||||
const payment = res.data;
|
||||
await this.setStateAsync({
|
||||
payment: payment,
|
||||
});
|
||||
if (payment.state === "Created") {
|
||||
if (["PayPal", "Stripe"].includes(payment.type)) {
|
||||
this.setState({
|
||||
timeout: setTimeout(async() => {
|
||||
await PaymentBackend.notifyPayment(this.state.owner, this.state.paymentName);
|
||||
this.getPayment();
|
||||
}, 1000),
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
timeout: setTimeout(() => this.getPayment(), 1000),
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
Setting.showMessage("error", err.message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
goToPaymentUrl(payment) {
|
||||
@ -81,7 +137,7 @@ class PaymentResultPage extends React.Component {
|
||||
|
||||
if (payment.state === "Paid") {
|
||||
return (
|
||||
<div>
|
||||
<div className="login-content">
|
||||
{
|
||||
Setting.renderHelmet(payment)
|
||||
}
|
||||
@ -101,7 +157,7 @@ class PaymentResultPage extends React.Component {
|
||||
);
|
||||
} else if (payment.state === "Created") {
|
||||
return (
|
||||
<div>
|
||||
<div className="login-content">
|
||||
{
|
||||
Setting.renderHelmet(payment)
|
||||
}
|
||||
@ -117,7 +173,7 @@ class PaymentResultPage extends React.Component {
|
||||
);
|
||||
} else if (payment.state === "Canceled") {
|
||||
return (
|
||||
<div>
|
||||
<div className="login-content">
|
||||
{
|
||||
Setting.renderHelmet(payment)
|
||||
}
|
||||
@ -137,7 +193,7 @@ class PaymentResultPage extends React.Component {
|
||||
);
|
||||
} else if (payment.state === "Timeout") {
|
||||
return (
|
||||
<div>
|
||||
<div className="login-content">
|
||||
{
|
||||
Setting.renderHelmet(payment)
|
||||
}
|
||||
@ -157,7 +213,7 @@ class PaymentResultPage extends React.Component {
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div>
|
||||
<div className="login-content">
|
||||
{
|
||||
Setting.renderHelmet(payment)
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ import {Button, Card, Col, Input, Row, Select, Switch} from "antd";
|
||||
import * as PermissionBackend from "./backend/PermissionBackend";
|
||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||
import * as UserBackend from "./backend/UserBackend";
|
||||
import * as GroupBackend from "./backend/GroupBackend";
|
||||
import * as Setting from "./Setting";
|
||||
import i18next from "i18next";
|
||||
import * as RoleBackend from "./backend/RoleBackend";
|
||||
@ -35,6 +36,7 @@ class PermissionEditPage extends React.Component {
|
||||
organizations: [],
|
||||
model: null,
|
||||
users: [],
|
||||
groups: [],
|
||||
roles: [],
|
||||
models: [],
|
||||
resources: [],
|
||||
@ -67,6 +69,7 @@ class PermissionEditPage extends React.Component {
|
||||
});
|
||||
|
||||
this.getUsers(permission.owner);
|
||||
this.getGroups(permission.owner);
|
||||
this.getRoles(permission.owner);
|
||||
this.getModels(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) {
|
||||
RoleBackend.getRoles(organizationName)
|
||||
.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 => {
|
||||
this.updatePermissionField("owner", owner);
|
||||
this.getUsers(owner);
|
||||
this.getGroups(owner);
|
||||
this.getRoles(owner);
|
||||
this.getModels(owner);
|
||||
this.getResources(owner);
|
||||
@ -263,6 +281,17 @@ class PermissionEditPage extends React.Component {
|
||||
/>
|
||||
</Col>
|
||||
</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"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("role:Sub roles"), i18next.t("role:Sub roles - Tooltip"))} :
|
||||
|
@ -33,6 +33,7 @@ class PermissionListPage extends BaseListPage {
|
||||
createdTime: moment().format(),
|
||||
displayName: `New Permission - ${randomName}`,
|
||||
users: [`${this.props.account.owner}/${this.props.account.name}`],
|
||||
groups: [],
|
||||
roles: [],
|
||||
domains: [],
|
||||
resourceType: "Application",
|
||||
@ -110,11 +111,12 @@ class PermissionListPage extends BaseListPage {
|
||||
|
||||
return (
|
||||
<Upload {...props}>
|
||||
<Button type="primary" size="small">
|
||||
<Button id="upload-button" type="primary" size="small">
|
||||
<UploadOutlined /> {i18next.t("user:Upload (.xlsx)")}
|
||||
</Button></Upload>
|
||||
);
|
||||
}
|
||||
|
||||
renderTable(permissions) {
|
||||
const columns = [
|
||||
// https://github.com/ant-design/ant-design/issues/22184
|
||||
@ -178,6 +180,17 @@ class PermissionListPage extends BaseListPage {
|
||||
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"),
|
||||
dataIndex: "roles",
|
||||
@ -361,7 +374,7 @@ class PermissionListPage extends BaseListPage {
|
||||
title={() => (
|
||||
<div>
|
||||
{i18next.t("general:Permissions")}
|
||||
<Button style={{marginRight: "5px"}} type="primary" size="small" onClick={this.addPermission.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||
<Button id="add-button" style={{marginRight: "5px"}} type="primary" size="small" onClick={this.addPermission.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||
{
|
||||
this.renderPermissionUpload()
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user