forked from casdoor/casdoor
Compare commits
11 Commits
copilot/fi
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d459403b7c | ||
|
|
cdeac335bb | ||
|
|
b81b502fbc | ||
|
|
df8e9fceea | ||
|
|
d674f0c33d | ||
|
|
1e1b5273d9 | ||
|
|
cf5e88915c | ||
|
|
c8973e6c9e | ||
|
|
87ea451561 | ||
|
|
8f32779b42 | ||
|
|
aba471b4e8 |
@@ -867,10 +867,16 @@ func (c *ApiController) Login() {
|
||||
return
|
||||
}
|
||||
|
||||
if application.IsSignupItemRequired("Invitation code") {
|
||||
c.ResponseError(c.T("check:Invitation code cannot be blank"))
|
||||
// Check and validate invitation code
|
||||
invitation, msg := object.CheckInvitationCode(application, organization, &authForm, c.GetAcceptLanguage())
|
||||
if msg != "" {
|
||||
c.ResponseError(msg)
|
||||
return
|
||||
}
|
||||
invitationName := ""
|
||||
if invitation != nil {
|
||||
invitationName = invitation.Name
|
||||
}
|
||||
|
||||
// Handle UseEmailAsUsername for OAuth and Web3
|
||||
if organization.UseEmailAsUsername && userInfo.Email != "" {
|
||||
@@ -937,11 +943,16 @@ func (c *ApiController) Login() {
|
||||
IsDeleted: false,
|
||||
SignupApplication: application.Name,
|
||||
Properties: properties,
|
||||
Invitation: invitationName,
|
||||
InvitationCode: authForm.InvitationCode,
|
||||
RegisterType: "Application Signup",
|
||||
RegisterSource: fmt.Sprintf("%s/%s", application.Organization, application.Name),
|
||||
}
|
||||
|
||||
if providerItem.SignupGroup != "" {
|
||||
// Set group from invitation code if available, otherwise use provider's signup group
|
||||
if invitation != nil && invitation.SignupGroup != "" {
|
||||
user.Groups = []string{invitation.SignupGroup}
|
||||
} else if providerItem.SignupGroup != "" {
|
||||
user.Groups = []string{providerItem.SignupGroup}
|
||||
}
|
||||
|
||||
@@ -956,6 +967,16 @@ func (c *ApiController) Login() {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:Failed to create user, user information is invalid: %s"), util.StructToJson(user)))
|
||||
return
|
||||
}
|
||||
|
||||
// Increment invitation usage count
|
||||
if invitation != nil {
|
||||
invitation.UsedCount += 1
|
||||
_, err = object.UpdateInvitation(invitation.GetId(), invitation, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sync info from 3rd-party if possible
|
||||
|
||||
@@ -303,6 +303,13 @@ func (c *ApiController) BatchEnforce() {
|
||||
c.ResponseOk(res, keyRes)
|
||||
}
|
||||
|
||||
// GetAllObjects
|
||||
// @Title GetAllObjects
|
||||
// @Tag Enforcer API
|
||||
// @Description Get all objects for a user (Casbin API)
|
||||
// @Param userId query string false "user id like built-in/admin"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /get-all-objects [get]
|
||||
func (c *ApiController) GetAllObjects() {
|
||||
userId := c.Ctx.Input.Query("userId")
|
||||
if userId == "" {
|
||||
@@ -322,6 +329,13 @@ func (c *ApiController) GetAllObjects() {
|
||||
c.ResponseOk(objects)
|
||||
}
|
||||
|
||||
// GetAllActions
|
||||
// @Title GetAllActions
|
||||
// @Tag Enforcer API
|
||||
// @Description Get all actions for a user (Casbin API)
|
||||
// @Param userId query string false "user id like built-in/admin"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /get-all-actions [get]
|
||||
func (c *ApiController) GetAllActions() {
|
||||
userId := c.Ctx.Input.Query("userId")
|
||||
if userId == "" {
|
||||
@@ -341,6 +355,13 @@ func (c *ApiController) GetAllActions() {
|
||||
c.ResponseOk(actions)
|
||||
}
|
||||
|
||||
// GetAllRoles
|
||||
// @Title GetAllRoles
|
||||
// @Tag Enforcer API
|
||||
// @Description Get all roles for a user (Casbin API)
|
||||
// @Param userId query string false "user id like built-in/admin"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /get-all-roles [get]
|
||||
func (c *ApiController) GetAllRoles() {
|
||||
userId := c.Ctx.Input.Query("userId")
|
||||
if userId == "" {
|
||||
|
||||
@@ -187,6 +187,22 @@ func (c *ApiController) SendVerificationCode() {
|
||||
return
|
||||
}
|
||||
|
||||
// Check if "Forgot password?" signin item is visible when using forget verification
|
||||
if vform.Method == ForgetVerification {
|
||||
isForgotPasswordEnabled := false
|
||||
for _, item := range application.SigninItems {
|
||||
if item.Name == "Forgot password?" {
|
||||
isForgotPasswordEnabled = item.Visible
|
||||
break
|
||||
}
|
||||
}
|
||||
// Block access if the signin item is not found or is explicitly hidden
|
||||
if !isForgotPasswordEnabled {
|
||||
c.ResponseError(c.T("verification:The forgot password feature is disabled"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
organization, err := object.GetOrganization(util.GetId(application.Owner, application.Organization))
|
||||
if err != nil {
|
||||
c.ResponseError(c.T(err.Error()))
|
||||
|
||||
2
go.mod
2
go.mod
@@ -14,6 +14,7 @@ require (
|
||||
github.com/alibabacloud-go/openapi-util v0.1.0
|
||||
github.com/alibabacloud-go/tea v1.3.2
|
||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.7
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.107
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.2+incompatible
|
||||
github.com/aliyun/credentials-go v1.3.10
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.95.0
|
||||
@@ -111,7 +112,6 @@ require (
|
||||
github.com/alibabacloud-go/tea-oss-utils v1.1.0 // indirect
|
||||
github.com/alibabacloud-go/tea-utils v1.3.6 // indirect
|
||||
github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.62.545 // indirect
|
||||
github.com/apistd/uni-go-sdk v0.0.2 // indirect
|
||||
github.com/atc0005/go-teams-notify/v2 v2.13.0 // indirect
|
||||
github.com/aws/aws-sdk-go v1.45.5 // indirect
|
||||
|
||||
8
go.sum
8
go.sum
@@ -766,8 +766,8 @@ github.com/alibabacloud-go/tea-xml v1.1.1/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCE
|
||||
github.com/alibabacloud-go/tea-xml v1.1.2/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
|
||||
github.com/alibabacloud-go/tea-xml v1.1.3 h1:7LYnm+JbOq2B+T/B0fHC4Ies4/FofC4zHzYtqw7dgt0=
|
||||
github.com/alibabacloud-go/tea-xml v1.1.3/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.62.545 h1:0LfzeUr4quwrrrTHn1kfLA0FBdsChCMs8eK2EzOwXVQ=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.62.545/go.mod h1:Api2AkmMgGaSUAhmk76oaFObkoeCPc/bKAqcyplPODs=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.107 h1:qagvUyrgOnBIlVRQWOyCZGVKUIYbMBdGdJ104vBpRFU=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.107/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.2+incompatible h1:9gWa46nstkJ9miBReJcN8Gq34cBFbzSpQZVVT9N09TM=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||
github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw=
|
||||
@@ -1294,7 +1294,6 @@ github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aW
|
||||
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
|
||||
github.com/jinzhu/configor v1.2.1 h1:OKk9dsR8i6HPOCZR8BcMtcEImAFjIhbJFZNyn5GCZko=
|
||||
github.com/jinzhu/configor v1.2.1/go.mod h1:nX89/MOmDba7ZX7GCyU/VIaQ2Ar2aizBl2d3JLF/rDc=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||
@@ -1308,7 +1307,6 @@ github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUB
|
||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA=
|
||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
@@ -2615,7 +2613,6 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
|
||||
gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
@@ -2637,6 +2634,7 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
||||
@@ -188,6 +188,7 @@
|
||||
"verification": {
|
||||
"Invalid captcha provider.": "Invalid captcha provider.",
|
||||
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
|
||||
"The forgot password feature is disabled": "The forgot password feature is disabled",
|
||||
"The verification code has already been used!": "The verification code has already been used!",
|
||||
"The verification code has not been sent yet!": "The verification code has not been sent yet!",
|
||||
"Turing test failed.": "Turing test failed.",
|
||||
|
||||
@@ -188,6 +188,7 @@
|
||||
"verification": {
|
||||
"Invalid captcha provider.": "非法的验证码提供商",
|
||||
"Phone number is invalid in your region %s": "您所在地区的电话号码无效 %s",
|
||||
"The forgot password feature is disabled": "忘记密码功能已被禁用",
|
||||
"The verification code has already been used!": "验证码已使用过!",
|
||||
"The verification code has not been sent yet!": "验证码未发送!",
|
||||
"Turing test failed.": "验证码还未发送",
|
||||
|
||||
@@ -26,6 +26,7 @@ type Order 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"`
|
||||
UpdateTime string `xorm:"varchar(100)" json:"updateTime"`
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
|
||||
// Product Info
|
||||
@@ -43,10 +44,6 @@ type Order struct {
|
||||
// Order State
|
||||
State string `xorm:"varchar(100)" json:"state"`
|
||||
Message string `xorm:"varchar(2000)" json:"message"`
|
||||
|
||||
// Order Duration
|
||||
StartTime string `xorm:"varchar(100)" json:"startTime"`
|
||||
EndTime string `xorm:"varchar(100)" json:"endTime"`
|
||||
}
|
||||
|
||||
type ProductInfo struct {
|
||||
@@ -138,6 +135,14 @@ func UpdateOrder(id string, order *Order) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if o.State != order.State {
|
||||
if order.State == "Created" {
|
||||
order.UpdateTime = ""
|
||||
} else {
|
||||
order.UpdateTime = util.GetCurrentTime()
|
||||
}
|
||||
}
|
||||
|
||||
if !slices.Equal(o.Products, order.Products) {
|
||||
existingInfos := make(map[string]ProductInfo, len(o.ProductInfos))
|
||||
for _, info := range o.ProductInfos {
|
||||
|
||||
@@ -99,8 +99,7 @@ func PlaceOrder(owner string, reqProductInfos []ProductInfo, user *User) (*Order
|
||||
Currency: orderCurrency,
|
||||
State: "Created",
|
||||
Message: "",
|
||||
StartTime: util.GetCurrentTime(),
|
||||
EndTime: "",
|
||||
UpdateTime: "",
|
||||
}
|
||||
|
||||
affected, err := AddOrder(order)
|
||||
@@ -344,7 +343,7 @@ func PayOrder(providerName, host, paymentEnv string, order *Order, lang string)
|
||||
if provider.Type == "Dummy" || provider.Type == "Balance" {
|
||||
order.State = "Paid"
|
||||
order.Message = "Payment successful"
|
||||
order.EndTime = util.GetCurrentTime()
|
||||
order.UpdateTime = util.GetCurrentTime()
|
||||
}
|
||||
|
||||
// Update order state first to avoid inconsistency
|
||||
@@ -371,6 +370,6 @@ func CancelOrder(order *Order) (bool, error) {
|
||||
|
||||
order.State = "Canceled"
|
||||
order.Message = "Canceled by user"
|
||||
order.EndTime = util.GetCurrentTime()
|
||||
order.UpdateTime = util.GetCurrentTime()
|
||||
return UpdateOrder(order.GetId(), order)
|
||||
}
|
||||
|
||||
@@ -301,16 +301,19 @@ func NotifyPayment(body []byte, owner string, paymentName string, lang string) (
|
||||
if payment.State == pp.PaymentStatePaid {
|
||||
order.State = "Paid"
|
||||
order.Message = "Payment successful"
|
||||
order.EndTime = util.GetCurrentTime()
|
||||
order.UpdateTime = util.GetCurrentTime()
|
||||
} else if payment.State == pp.PaymentStateError {
|
||||
order.State = "PaymentFailed"
|
||||
order.Message = payment.Message
|
||||
order.UpdateTime = util.GetCurrentTime()
|
||||
} else if payment.State == pp.PaymentStateCanceled {
|
||||
order.State = "Canceled"
|
||||
order.Message = "Payment was cancelled"
|
||||
order.UpdateTime = util.GetCurrentTime()
|
||||
} else if payment.State == pp.PaymentStateTimeout {
|
||||
order.State = "Timeout"
|
||||
order.Message = "Payment timed out"
|
||||
order.UpdateTime = util.GetCurrentTime()
|
||||
}
|
||||
_, err = UpdateOrder(order.GetId(), order)
|
||||
if err != nil {
|
||||
|
||||
@@ -175,8 +175,10 @@ func DeleteSession(id, curSessionId string) (bool, error) {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// If session doesn't exist, return success with no rows affected
|
||||
// This is a valid state (e.g., when a user has no active session)
|
||||
if session == nil {
|
||||
return false, fmt.Errorf("session is nil")
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if slices.Contains(session.SessionId, curSessionId) {
|
||||
|
||||
@@ -28,6 +28,8 @@ func getSmsClient(provider *Provider) (sender.SmsClient, error) {
|
||||
client, err = sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.ProviderUrl, provider.AppId)
|
||||
} else if provider.Type == "Custom HTTP SMS" {
|
||||
client, err = newHttpSmsClient(provider.Endpoint, provider.Method, provider.Title, provider.TemplateCode, provider.HttpHeaders, provider.UserMapping, provider.IssuerUrl, provider.EnableProxy)
|
||||
} else if provider.Type == "Alibaba Cloud PNVS SMS" {
|
||||
client, err = newPnvsSmsClient(provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.RegionId)
|
||||
} else {
|
||||
client, err = sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.AppId)
|
||||
}
|
||||
@@ -48,7 +50,7 @@ func SendSms(provider *Provider, content string, phoneNumbers ...string) error {
|
||||
if provider.AppId != "" {
|
||||
phoneNumbers = append([]string{provider.AppId}, phoneNumbers...)
|
||||
}
|
||||
} else if provider.Type == sender.Aliyun {
|
||||
} else if provider.Type == sender.Aliyun || provider.Type == "Alibaba Cloud PNVS SMS" {
|
||||
for i, number := range phoneNumbers {
|
||||
phoneNumbers[i] = strings.TrimPrefix(number, "+86")
|
||||
}
|
||||
|
||||
86
object/sms_pnvs.go
Normal file
86
object/sms_pnvs.go
Normal file
@@ -0,0 +1,86 @@
|
||||
// Copyright 2026 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package object
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/services/dypnsapi"
|
||||
)
|
||||
|
||||
type PnvsSmsClient struct {
|
||||
template string
|
||||
sign string
|
||||
core *dypnsapi.Client
|
||||
}
|
||||
|
||||
func newPnvsSmsClient(accessId string, accessKey string, sign string, template string, regionId string) (*PnvsSmsClient, error) {
|
||||
if regionId == "" {
|
||||
regionId = "cn-hangzhou"
|
||||
}
|
||||
|
||||
client, err := dypnsapi.NewClientWithAccessKey(regionId, accessId, accessKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pnvsClient := &PnvsSmsClient{
|
||||
template: template,
|
||||
core: client,
|
||||
sign: sign,
|
||||
}
|
||||
|
||||
return pnvsClient, nil
|
||||
}
|
||||
|
||||
func (c *PnvsSmsClient) SendMessage(param map[string]string, targetPhoneNumber ...string) error {
|
||||
if len(targetPhoneNumber) == 0 {
|
||||
return fmt.Errorf("missing parameter: targetPhoneNumber")
|
||||
}
|
||||
|
||||
// PNVS sends to one phone number at a time
|
||||
phoneNumber := targetPhoneNumber[0]
|
||||
|
||||
request := dypnsapi.CreateSendSmsVerifyCodeRequest()
|
||||
request.Scheme = "https"
|
||||
request.PhoneNumber = phoneNumber
|
||||
request.TemplateCode = c.template
|
||||
request.SignName = c.sign
|
||||
|
||||
// TemplateParam is optional for PNVS as it can auto-generate verification codes
|
||||
// But if params are provided, we'll pass them
|
||||
if len(param) > 0 {
|
||||
templateParam, err := json.Marshal(param)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
request.TemplateParam = string(templateParam)
|
||||
}
|
||||
|
||||
response, err := c.core.SendSmsVerifyCode(request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if response.Code != "OK" {
|
||||
if response.Message != "" {
|
||||
return fmt.Errorf(response.Message)
|
||||
}
|
||||
return fmt.Errorf("PNVS SMS send failed with code: %s", response.Code)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -370,3 +370,15 @@ func (p *ActiveDirectorySyncerProvider) adEntryToOriginalUser(entry *goldap.Entr
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
// GetOriginalGroups retrieves all groups from Active Directory (not implemented yet)
|
||||
func (p *ActiveDirectorySyncerProvider) GetOriginalGroups() ([]*OriginalGroup, error) {
|
||||
// TODO: Implement Active Directory group sync
|
||||
return []*OriginalGroup{}, nil
|
||||
}
|
||||
|
||||
// GetOriginalUserGroups retrieves the group IDs that a user belongs to (not implemented yet)
|
||||
func (p *ActiveDirectorySyncerProvider) GetOriginalUserGroups(userId string) ([]string, error) {
|
||||
// TODO: Implement Active Directory user group membership sync
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
@@ -274,3 +274,15 @@ func (p *AzureAdSyncerProvider) getAzureAdOriginalUsers() ([]*OriginalUser, erro
|
||||
|
||||
return originalUsers, nil
|
||||
}
|
||||
|
||||
// GetOriginalGroups retrieves all groups from Azure AD (not implemented yet)
|
||||
func (p *AzureAdSyncerProvider) GetOriginalGroups() ([]*OriginalGroup, error) {
|
||||
// TODO: Implement Azure AD group sync
|
||||
return []*OriginalGroup{}, nil
|
||||
}
|
||||
|
||||
// GetOriginalUserGroups retrieves the group IDs that a user belongs to (not implemented yet)
|
||||
func (p *AzureAdSyncerProvider) GetOriginalUserGroups(userId string) ([]string, error) {
|
||||
// TODO: Implement Azure AD user group membership sync
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
@@ -60,9 +60,19 @@ func addSyncerJob(syncer *Syncer) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Sync groups as well
|
||||
err = syncer.syncGroups()
|
||||
if err != nil {
|
||||
// Log error but don't fail the entire sync
|
||||
fmt.Printf("Warning: syncGroups() error: %s\n", err.Error())
|
||||
}
|
||||
|
||||
schedule := fmt.Sprintf("@every %ds", syncer.SyncInterval)
|
||||
cron := getCronMap(syncer.Name)
|
||||
_, err = cron.AddFunc(schedule, syncer.syncUsersNoError)
|
||||
_, err = cron.AddFunc(schedule, func() {
|
||||
syncer.syncUsersNoError()
|
||||
syncer.syncGroupsNoError()
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -164,3 +164,15 @@ func (t dsnConnector) Connect(ctx context.Context) (driver.Conn, error) {
|
||||
func (t dsnConnector) Driver() driver.Driver {
|
||||
return t.driver
|
||||
}
|
||||
|
||||
// GetOriginalGroups retrieves all groups from Database (not implemented yet)
|
||||
func (p *DatabaseSyncerProvider) GetOriginalGroups() ([]*OriginalGroup, error) {
|
||||
// TODO: Implement Database group sync
|
||||
return []*OriginalGroup{}, nil
|
||||
}
|
||||
|
||||
// GetOriginalUserGroups retrieves the group IDs that a user belongs to (not implemented yet)
|
||||
func (p *DatabaseSyncerProvider) GetOriginalUserGroups(userId string) ([]string, error) {
|
||||
// TODO: Implement Database user group membership sync
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
@@ -384,3 +384,15 @@ func (p *DingtalkSyncerProvider) dingtalkUserToOriginalUser(dingtalkUser *Dingta
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
// GetOriginalGroups retrieves all groups from DingTalk (not implemented yet)
|
||||
func (p *DingtalkSyncerProvider) GetOriginalGroups() ([]*OriginalGroup, error) {
|
||||
// TODO: Implement DingTalk group sync
|
||||
return []*OriginalGroup{}, nil
|
||||
}
|
||||
|
||||
// GetOriginalUserGroups retrieves the group IDs that a user belongs to (not implemented yet)
|
||||
func (p *DingtalkSyncerProvider) GetOriginalUserGroups(userId string) ([]string, error) {
|
||||
// TODO: Implement DingTalk user group membership sync
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
@@ -101,6 +101,7 @@ func (p *GoogleWorkspaceSyncerProvider) getAdminService() (*admin.Service, error
|
||||
PrivateKey: []byte(serviceAccount.PrivateKey),
|
||||
Scopes: []string{
|
||||
admin.AdminDirectoryUserReadonlyScope,
|
||||
admin.AdminDirectoryGroupReadonlyScope,
|
||||
},
|
||||
TokenURL: google.JWTTokenURL,
|
||||
Subject: adminEmail, // Impersonate the admin user
|
||||
@@ -202,12 +203,189 @@ func (p *GoogleWorkspaceSyncerProvider) getGoogleWorkspaceOriginalUsers() ([]*Or
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get all groups and their members to build a user-to-groups mapping
|
||||
// This avoids N+1 queries by fetching group memberships upfront
|
||||
userGroupsMap, err := p.buildUserGroupsMap(service)
|
||||
if err != nil {
|
||||
fmt.Printf("Warning: failed to fetch group memberships: %v. Users will have no groups assigned.\n", err)
|
||||
userGroupsMap = make(map[string][]string)
|
||||
}
|
||||
|
||||
// Convert Google Workspace users to Casdoor OriginalUser
|
||||
originalUsers := []*OriginalUser{}
|
||||
for _, gwUser := range gwUsers {
|
||||
originalUser := p.googleWorkspaceUserToOriginalUser(gwUser)
|
||||
|
||||
// Assign groups from the pre-built map
|
||||
if groups, exists := userGroupsMap[gwUser.PrimaryEmail]; exists {
|
||||
originalUser.Groups = groups
|
||||
} else {
|
||||
originalUser.Groups = []string{}
|
||||
}
|
||||
|
||||
originalUsers = append(originalUsers, originalUser)
|
||||
}
|
||||
|
||||
return originalUsers, nil
|
||||
}
|
||||
|
||||
// buildUserGroupsMap builds a map of user email to group emails by iterating through all groups
|
||||
// and their members. This is more efficient than querying groups for each user individually.
|
||||
func (p *GoogleWorkspaceSyncerProvider) buildUserGroupsMap(service *admin.Service) (map[string][]string, error) {
|
||||
userGroupsMap := make(map[string][]string)
|
||||
|
||||
// Get all groups
|
||||
groups, err := p.getGoogleWorkspaceGroups(service)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch groups: %v", err)
|
||||
}
|
||||
|
||||
// For each group, get its members and populate the user-to-groups map
|
||||
for _, group := range groups {
|
||||
members, err := p.getGroupMembers(service, group.Id)
|
||||
if err != nil {
|
||||
fmt.Printf("Warning: failed to get members for group %s: %v\n", group.Email, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Add this group to each member's group list
|
||||
for _, member := range members {
|
||||
userGroupsMap[member.Email] = append(userGroupsMap[member.Email], group.Email)
|
||||
}
|
||||
}
|
||||
|
||||
return userGroupsMap, nil
|
||||
}
|
||||
|
||||
// getGroupMembers retrieves all members of a specific group
|
||||
func (p *GoogleWorkspaceSyncerProvider) getGroupMembers(service *admin.Service, groupId string) ([]*admin.Member, error) {
|
||||
allMembers := []*admin.Member{}
|
||||
pageToken := ""
|
||||
|
||||
for {
|
||||
call := service.Members.List(groupId).MaxResults(500)
|
||||
if pageToken != "" {
|
||||
call = call.PageToken(pageToken)
|
||||
}
|
||||
|
||||
resp, err := call.Do()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list members: %v", err)
|
||||
}
|
||||
|
||||
allMembers = append(allMembers, resp.Members...)
|
||||
|
||||
// Handle pagination
|
||||
if resp.NextPageToken == "" {
|
||||
break
|
||||
}
|
||||
pageToken = resp.NextPageToken
|
||||
}
|
||||
|
||||
return allMembers, nil
|
||||
}
|
||||
|
||||
// GetOriginalGroups retrieves all groups from Google Workspace
|
||||
func (p *GoogleWorkspaceSyncerProvider) GetOriginalGroups() ([]*OriginalGroup, error) {
|
||||
// Get Admin SDK service
|
||||
service, err := p.getAdminService()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get all groups from Google Workspace
|
||||
gwGroups, err := p.getGoogleWorkspaceGroups(service)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert Google Workspace groups to Casdoor OriginalGroup
|
||||
originalGroups := []*OriginalGroup{}
|
||||
for _, gwGroup := range gwGroups {
|
||||
originalGroup := p.googleWorkspaceGroupToOriginalGroup(gwGroup)
|
||||
originalGroups = append(originalGroups, originalGroup)
|
||||
}
|
||||
|
||||
return originalGroups, nil
|
||||
}
|
||||
|
||||
// GetOriginalUserGroups retrieves the group IDs that a user belongs to
|
||||
func (p *GoogleWorkspaceSyncerProvider) GetOriginalUserGroups(userId string) ([]string, error) {
|
||||
// Get Admin SDK service
|
||||
service, err := p.getAdminService()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get groups for the user
|
||||
groupIds := []string{}
|
||||
pageToken := ""
|
||||
|
||||
for {
|
||||
call := service.Groups.List().UserKey(userId)
|
||||
if pageToken != "" {
|
||||
call = call.PageToken(pageToken)
|
||||
}
|
||||
|
||||
resp, err := call.Do()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list user groups: %v", err)
|
||||
}
|
||||
|
||||
for _, group := range resp.Groups {
|
||||
groupIds = append(groupIds, group.Email)
|
||||
}
|
||||
|
||||
// Handle pagination
|
||||
if resp.NextPageToken == "" {
|
||||
break
|
||||
}
|
||||
pageToken = resp.NextPageToken
|
||||
}
|
||||
|
||||
return groupIds, nil
|
||||
}
|
||||
|
||||
// getGoogleWorkspaceGroups gets all groups from Google Workspace using Admin SDK API
|
||||
func (p *GoogleWorkspaceSyncerProvider) getGoogleWorkspaceGroups(service *admin.Service) ([]*admin.Group, error) {
|
||||
allGroups := []*admin.Group{}
|
||||
pageToken := ""
|
||||
|
||||
// Get the customer ID (use "my_customer" for the domain)
|
||||
customer := "my_customer"
|
||||
|
||||
for {
|
||||
call := service.Groups.List().Customer(customer).MaxResults(500)
|
||||
if pageToken != "" {
|
||||
call = call.PageToken(pageToken)
|
||||
}
|
||||
|
||||
resp, err := call.Do()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list groups: %v", err)
|
||||
}
|
||||
|
||||
allGroups = append(allGroups, resp.Groups...)
|
||||
|
||||
// Handle pagination
|
||||
if resp.NextPageToken == "" {
|
||||
break
|
||||
}
|
||||
pageToken = resp.NextPageToken
|
||||
}
|
||||
|
||||
return allGroups, nil
|
||||
}
|
||||
|
||||
// googleWorkspaceGroupToOriginalGroup converts Google Workspace group to Casdoor OriginalGroup
|
||||
func (p *GoogleWorkspaceSyncerProvider) googleWorkspaceGroupToOriginalGroup(gwGroup *admin.Group) *OriginalGroup {
|
||||
group := &OriginalGroup{
|
||||
Id: gwGroup.Id,
|
||||
Name: gwGroup.Email,
|
||||
DisplayName: gwGroup.Name,
|
||||
Description: gwGroup.Description,
|
||||
Email: gwGroup.Email,
|
||||
}
|
||||
|
||||
return group
|
||||
}
|
||||
|
||||
204
object/syncer_googleworkspace_test.go
Normal file
204
object/syncer_googleworkspace_test.go
Normal file
@@ -0,0 +1,204 @@
|
||||
// Copyright 2025 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package object
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
admin "google.golang.org/api/admin/directory/v1"
|
||||
)
|
||||
|
||||
func TestGoogleWorkspaceUserToOriginalUser(t *testing.T) {
|
||||
provider := &GoogleWorkspaceSyncerProvider{
|
||||
Syncer: &Syncer{},
|
||||
}
|
||||
|
||||
// Test case 1: Full Google Workspace user with all fields
|
||||
gwUser := &admin.User{
|
||||
Id: "user-123",
|
||||
PrimaryEmail: "john.doe@example.com",
|
||||
Name: &admin.UserName{
|
||||
FullName: "John Doe",
|
||||
GivenName: "John",
|
||||
FamilyName: "Doe",
|
||||
},
|
||||
ThumbnailPhotoUrl: "https://example.com/avatar.jpg",
|
||||
Suspended: false,
|
||||
IsAdmin: true,
|
||||
CreationTime: "2024-01-01T00:00:00Z",
|
||||
}
|
||||
|
||||
originalUser := provider.googleWorkspaceUserToOriginalUser(gwUser)
|
||||
|
||||
// Verify basic fields
|
||||
if originalUser.Id != "user-123" {
|
||||
t.Errorf("Expected Id to be 'user-123', got '%s'", originalUser.Id)
|
||||
}
|
||||
if originalUser.Name != "john.doe@example.com" {
|
||||
t.Errorf("Expected Name to be 'john.doe@example.com', got '%s'", originalUser.Name)
|
||||
}
|
||||
if originalUser.Email != "john.doe@example.com" {
|
||||
t.Errorf("Expected Email to be 'john.doe@example.com', got '%s'", originalUser.Email)
|
||||
}
|
||||
if originalUser.DisplayName != "John Doe" {
|
||||
t.Errorf("Expected DisplayName to be 'John Doe', got '%s'", originalUser.DisplayName)
|
||||
}
|
||||
if originalUser.FirstName != "John" {
|
||||
t.Errorf("Expected FirstName to be 'John', got '%s'", originalUser.FirstName)
|
||||
}
|
||||
if originalUser.LastName != "Doe" {
|
||||
t.Errorf("Expected LastName to be 'Doe', got '%s'", originalUser.LastName)
|
||||
}
|
||||
if originalUser.Avatar != "https://example.com/avatar.jpg" {
|
||||
t.Errorf("Expected Avatar to be 'https://example.com/avatar.jpg', got '%s'", originalUser.Avatar)
|
||||
}
|
||||
if originalUser.IsForbidden != false {
|
||||
t.Errorf("Expected IsForbidden to be false for non-suspended user, got %v", originalUser.IsForbidden)
|
||||
}
|
||||
if originalUser.IsAdmin != true {
|
||||
t.Errorf("Expected IsAdmin to be true, got %v", originalUser.IsAdmin)
|
||||
}
|
||||
|
||||
// Test case 2: Suspended Google Workspace user
|
||||
suspendedUser := &admin.User{
|
||||
Id: "user-456",
|
||||
PrimaryEmail: "jane.doe@example.com",
|
||||
Name: &admin.UserName{
|
||||
FullName: "Jane Doe",
|
||||
},
|
||||
Suspended: true,
|
||||
}
|
||||
|
||||
suspendedOriginalUser := provider.googleWorkspaceUserToOriginalUser(suspendedUser)
|
||||
if suspendedOriginalUser.IsForbidden != true {
|
||||
t.Errorf("Expected IsForbidden to be true for suspended user, got %v", suspendedOriginalUser.IsForbidden)
|
||||
}
|
||||
|
||||
// Test case 3: User with no Name object (should not panic)
|
||||
minimalUser := &admin.User{
|
||||
Id: "user-789",
|
||||
PrimaryEmail: "bob@example.com",
|
||||
}
|
||||
|
||||
minimalOriginalUser := provider.googleWorkspaceUserToOriginalUser(minimalUser)
|
||||
if minimalOriginalUser.DisplayName != "" {
|
||||
t.Errorf("Expected DisplayName to be empty for minimal user, got '%s'", minimalOriginalUser.DisplayName)
|
||||
}
|
||||
|
||||
// Test case 4: Display name construction from first/last name when FullName is empty
|
||||
noFullNameUser := &admin.User{
|
||||
Id: "user-101",
|
||||
PrimaryEmail: "alice@example.com",
|
||||
Name: &admin.UserName{
|
||||
GivenName: "Alice",
|
||||
FamilyName: "Jones",
|
||||
},
|
||||
}
|
||||
|
||||
noFullNameOriginalUser := provider.googleWorkspaceUserToOriginalUser(noFullNameUser)
|
||||
if noFullNameOriginalUser.DisplayName != "Alice Jones" {
|
||||
t.Errorf("Expected DisplayName to be constructed as 'Alice Jones', got '%s'", noFullNameOriginalUser.DisplayName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoogleWorkspaceGroupToOriginalGroup(t *testing.T) {
|
||||
provider := &GoogleWorkspaceSyncerProvider{
|
||||
Syncer: &Syncer{},
|
||||
}
|
||||
|
||||
// Test case 1: Full Google Workspace group with all fields
|
||||
gwGroup := &admin.Group{
|
||||
Id: "group-123",
|
||||
Email: "team@example.com",
|
||||
Name: "Engineering Team",
|
||||
Description: "All engineering staff",
|
||||
}
|
||||
|
||||
originalGroup := provider.googleWorkspaceGroupToOriginalGroup(gwGroup)
|
||||
|
||||
// Verify all fields
|
||||
if originalGroup.Id != "group-123" {
|
||||
t.Errorf("Expected Id to be 'group-123', got '%s'", originalGroup.Id)
|
||||
}
|
||||
if originalGroup.Name != "team@example.com" {
|
||||
t.Errorf("Expected Name to be 'team@example.com', got '%s'", originalGroup.Name)
|
||||
}
|
||||
if originalGroup.DisplayName != "Engineering Team" {
|
||||
t.Errorf("Expected DisplayName to be 'Engineering Team', got '%s'", originalGroup.DisplayName)
|
||||
}
|
||||
if originalGroup.Description != "All engineering staff" {
|
||||
t.Errorf("Expected Description to be 'All engineering staff', got '%s'", originalGroup.Description)
|
||||
}
|
||||
if originalGroup.Email != "team@example.com" {
|
||||
t.Errorf("Expected Email to be 'team@example.com', got '%s'", originalGroup.Email)
|
||||
}
|
||||
|
||||
// Test case 2: Minimal group
|
||||
minimalGroup := &admin.Group{
|
||||
Id: "group-456",
|
||||
Email: "minimal@example.com",
|
||||
}
|
||||
|
||||
minimalOriginalGroup := provider.googleWorkspaceGroupToOriginalGroup(minimalGroup)
|
||||
if minimalOriginalGroup.DisplayName != "" {
|
||||
t.Errorf("Expected DisplayName to be empty for minimal group, got '%s'", minimalOriginalGroup.DisplayName)
|
||||
}
|
||||
if minimalOriginalGroup.Description != "" {
|
||||
t.Errorf("Expected Description to be empty for minimal group, got '%s'", minimalOriginalGroup.Description)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSyncerProviderGoogleWorkspace(t *testing.T) {
|
||||
syncer := &Syncer{
|
||||
Type: "Google Workspace",
|
||||
Host: "admin@example.com",
|
||||
}
|
||||
|
||||
provider := GetSyncerProvider(syncer)
|
||||
|
||||
if _, ok := provider.(*GoogleWorkspaceSyncerProvider); !ok {
|
||||
t.Errorf("Expected GoogleWorkspaceSyncerProvider for type 'Google Workspace', got %T", provider)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoogleWorkspaceSyncerProviderEmptyMethods(t *testing.T) {
|
||||
provider := &GoogleWorkspaceSyncerProvider{
|
||||
Syncer: &Syncer{},
|
||||
}
|
||||
|
||||
// Test AddUser returns error
|
||||
_, err := provider.AddUser(&OriginalUser{})
|
||||
if err == nil {
|
||||
t.Error("Expected AddUser to return error for read-only syncer")
|
||||
}
|
||||
|
||||
// Test UpdateUser returns error
|
||||
_, err = provider.UpdateUser(&OriginalUser{})
|
||||
if err == nil {
|
||||
t.Error("Expected UpdateUser to return error for read-only syncer")
|
||||
}
|
||||
|
||||
// Test Close returns no error
|
||||
err = provider.Close()
|
||||
if err != nil {
|
||||
t.Errorf("Expected Close to return nil, got error: %v", err)
|
||||
}
|
||||
|
||||
// Test InitAdapter returns no error
|
||||
err = provider.InitAdapter()
|
||||
if err != nil {
|
||||
t.Errorf("Expected InitAdapter to return nil, got error: %v", err)
|
||||
}
|
||||
}
|
||||
121
object/syncer_group.go
Normal file
121
object/syncer_group.go
Normal file
@@ -0,0 +1,121 @@
|
||||
// Copyright 2025 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
func (syncer *Syncer) getOriginalGroups() ([]*OriginalGroup, error) {
|
||||
provider := GetSyncerProvider(syncer)
|
||||
return provider.GetOriginalGroups()
|
||||
}
|
||||
|
||||
func (syncer *Syncer) createGroupFromOriginalGroup(originalGroup *OriginalGroup) *Group {
|
||||
group := &Group{
|
||||
Owner: syncer.Organization,
|
||||
Name: originalGroup.Name,
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
UpdatedTime: util.GetCurrentTime(),
|
||||
DisplayName: originalGroup.DisplayName,
|
||||
Type: originalGroup.Type,
|
||||
Manager: originalGroup.Manager,
|
||||
IsEnabled: true,
|
||||
IsTopGroup: true,
|
||||
}
|
||||
|
||||
if originalGroup.Email != "" {
|
||||
group.ContactEmail = originalGroup.Email
|
||||
}
|
||||
|
||||
return group
|
||||
}
|
||||
|
||||
func (syncer *Syncer) syncGroups() error {
|
||||
fmt.Printf("Running syncGroups()..\n")
|
||||
|
||||
// Get existing groups from Casdoor
|
||||
groups, err := GetGroups(syncer.Organization)
|
||||
if err != nil {
|
||||
line := fmt.Sprintf("[%s] %s\n", util.GetCurrentTime(), err.Error())
|
||||
_, err2 := updateSyncerErrorText(syncer, line)
|
||||
if err2 != nil {
|
||||
panic(err2)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Get groups from the external system
|
||||
oGroups, err := syncer.getOriginalGroups()
|
||||
if err != nil {
|
||||
line := fmt.Sprintf("[%s] %s\n", util.GetCurrentTime(), err.Error())
|
||||
_, err2 := updateSyncerErrorText(syncer, line)
|
||||
if err2 != nil {
|
||||
panic(err2)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Groups: %d, oGroups: %d\n", len(groups), len(oGroups))
|
||||
|
||||
// Create a map of existing groups by name
|
||||
myGroups := map[string]*Group{}
|
||||
for _, group := range groups {
|
||||
myGroups[group.Name] = group
|
||||
}
|
||||
|
||||
// Sync groups from external system to Casdoor
|
||||
newGroups := []*Group{}
|
||||
for _, oGroup := range oGroups {
|
||||
if _, ok := myGroups[oGroup.Name]; !ok {
|
||||
newGroup := syncer.createGroupFromOriginalGroup(oGroup)
|
||||
fmt.Printf("New group: %v\n", newGroup)
|
||||
newGroups = append(newGroups, newGroup)
|
||||
} else {
|
||||
// Group already exists, could update it here if needed
|
||||
existingGroup := myGroups[oGroup.Name]
|
||||
|
||||
// Update group display name and other fields if they've changed
|
||||
if existingGroup.DisplayName != oGroup.DisplayName {
|
||||
existingGroup.DisplayName = oGroup.DisplayName
|
||||
existingGroup.UpdatedTime = util.GetCurrentTime()
|
||||
_, err = UpdateGroup(existingGroup.GetId(), existingGroup)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to update group %s: %v\n", existingGroup.Name, err)
|
||||
} else {
|
||||
fmt.Printf("Updated group: %s\n", existingGroup.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(newGroups) != 0 {
|
||||
_, err = AddGroupsInBatch(newGroups)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (syncer *Syncer) syncGroupsNoError() {
|
||||
err := syncer.syncGroups()
|
||||
if err != nil {
|
||||
fmt.Printf("syncGroupsNoError() error: %s\n", err.Error())
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,17 @@
|
||||
|
||||
package object
|
||||
|
||||
// OriginalGroup represents a group from an external system
|
||||
type OriginalGroup struct {
|
||||
Id string
|
||||
Name string
|
||||
DisplayName string
|
||||
Description string
|
||||
Type string
|
||||
Manager string
|
||||
Email string
|
||||
}
|
||||
|
||||
// SyncerProvider defines the interface that all syncer implementations must satisfy.
|
||||
// Different syncer types (Database, Keycloak, WeCom, Azure AD) implement this interface.
|
||||
type SyncerProvider interface {
|
||||
@@ -23,6 +34,12 @@ type SyncerProvider interface {
|
||||
// GetOriginalUsers retrieves all users from the external system
|
||||
GetOriginalUsers() ([]*OriginalUser, error)
|
||||
|
||||
// GetOriginalGroups retrieves all groups from the external system
|
||||
GetOriginalGroups() ([]*OriginalGroup, error)
|
||||
|
||||
// GetOriginalUserGroups retrieves the group IDs that a user belongs to
|
||||
GetOriginalUserGroups(userId string) ([]string, error)
|
||||
|
||||
// AddUser adds a new user to the external system
|
||||
AddUser(user *OriginalUser) (bool, error)
|
||||
|
||||
|
||||
@@ -29,3 +29,15 @@ func (p *KeycloakSyncerProvider) GetOriginalUsers() ([]*OriginalUser, error) {
|
||||
|
||||
// Note: Keycloak-specific user mapping is handled in syncer_util.go
|
||||
// via getOriginalUsersFromMap which checks syncer.Type == "Keycloak"
|
||||
|
||||
// GetOriginalGroups retrieves all groups from Keycloak (not implemented yet)
|
||||
func (p *KeycloakSyncerProvider) GetOriginalGroups() ([]*OriginalGroup, error) {
|
||||
// TODO: Implement Keycloak group sync
|
||||
return []*OriginalGroup{}, nil
|
||||
}
|
||||
|
||||
// GetOriginalUserGroups retrieves the group IDs that a user belongs to (not implemented yet)
|
||||
func (p *KeycloakSyncerProvider) GetOriginalUserGroups(userId string) ([]string, error) {
|
||||
// TODO: Implement Keycloak user group membership sync
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
@@ -414,3 +414,15 @@ func (p *LarkSyncerProvider) larkUserToOriginalUser(larkUser *LarkUser) *Origina
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
// GetOriginalGroups retrieves all groups from Lark (not implemented yet)
|
||||
func (p *LarkSyncerProvider) GetOriginalGroups() ([]*OriginalGroup, error) {
|
||||
// TODO: Implement Lark group sync
|
||||
return []*OriginalGroup{}, nil
|
||||
}
|
||||
|
||||
// GetOriginalUserGroups retrieves the group IDs that a user belongs to (not implemented yet)
|
||||
func (p *LarkSyncerProvider) GetOriginalUserGroups(userId string) ([]string, error) {
|
||||
// TODO: Implement Lark user group membership sync
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
@@ -296,3 +296,15 @@ func (p *OktaSyncerProvider) getOktaOriginalUsers() ([]*OriginalUser, error) {
|
||||
|
||||
return originalUsers, nil
|
||||
}
|
||||
|
||||
// GetOriginalGroups retrieves all groups from Okta (not implemented yet)
|
||||
func (p *OktaSyncerProvider) GetOriginalGroups() ([]*OriginalGroup, error) {
|
||||
// TODO: Implement Okta group sync
|
||||
return []*OriginalGroup{}, nil
|
||||
}
|
||||
|
||||
// GetOriginalUserGroups retrieves the group IDs that a user belongs to (not implemented yet)
|
||||
func (p *OktaSyncerProvider) GetOriginalUserGroups(userId string) ([]string, error) {
|
||||
// TODO: Implement Okta user group membership sync
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
@@ -335,3 +335,15 @@ func (p *SCIMSyncerProvider) scimUserToOriginalUser(scimUser *SCIMUser) *Origina
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
// GetOriginalGroups retrieves all groups from SCIM (not implemented yet)
|
||||
func (p *SCIMSyncerProvider) GetOriginalGroups() ([]*OriginalGroup, error) {
|
||||
// TODO: Implement SCIM group sync
|
||||
return []*OriginalGroup{}, nil
|
||||
}
|
||||
|
||||
// GetOriginalUserGroups retrieves the group IDs that a user belongs to (not implemented yet)
|
||||
func (p *SCIMSyncerProvider) GetOriginalUserGroups(userId string) ([]string, error) {
|
||||
// TODO: Implement SCIM user group membership sync
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
@@ -303,3 +303,15 @@ func (p *WecomSyncerProvider) wecomUserToOriginalUser(wecomUser *WecomUser) *Ori
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
// GetOriginalGroups retrieves all groups from WeCom (not implemented yet)
|
||||
func (p *WecomSyncerProvider) GetOriginalGroups() ([]*OriginalGroup, error) {
|
||||
// TODO: Implement WeCom group sync
|
||||
return []*OriginalGroup{}, nil
|
||||
}
|
||||
|
||||
// GetOriginalUserGroups retrieves the group IDs that a user belongs to (not implemented yet)
|
||||
func (p *WecomSyncerProvider) GetOriginalUserGroups(userId string) ([]string, error) {
|
||||
// TODO: Implement WeCom user group membership sync
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
@@ -850,6 +850,69 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/add-ticket": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Ticket API"
|
||||
],
|
||||
"description": "add ticket",
|
||||
"operationId": "ApiController.AddTicket",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "body",
|
||||
"name": "body",
|
||||
"description": "The details of the ticket",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/object.Ticket"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controllers.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/add-ticket-message": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Ticket API"
|
||||
],
|
||||
"description": "add a message to a ticket",
|
||||
"operationId": "ApiController.AddTicketMessage",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "id",
|
||||
"description": "The id ( owner/name ) of the ticket",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"in": "body",
|
||||
"name": "body",
|
||||
"description": "The message to add",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/object.TicketMessage"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controllers.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/add-token": {
|
||||
"post": {
|
||||
"tags": [
|
||||
@@ -1707,6 +1770,34 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/delete-ticket": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Ticket API"
|
||||
],
|
||||
"description": "delete ticket",
|
||||
"operationId": "ApiController.DeleteTicket",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "body",
|
||||
"name": "body",
|
||||
"description": "The details of the ticket",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/object.Ticket"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controllers.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/delete-token": {
|
||||
"post": {
|
||||
"tags": [
|
||||
@@ -1891,6 +1982,23 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/exit-impersonation-user": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"User API"
|
||||
],
|
||||
"description": "clear impersonation info for current session",
|
||||
"operationId": "ApiController.ExitImpersonateUser",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controllers.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/faceid-signin-begin": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -1996,6 +2104,81 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/get-all-actions": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Enforcer API"
|
||||
],
|
||||
"description": "Get all actions for a user (Casbin API)",
|
||||
"operationId": "ApiController.GetAllActions",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "userId",
|
||||
"description": "user id like built-in/admin",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controllers.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/get-all-objects": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Enforcer API"
|
||||
],
|
||||
"description": "Get all objects for a user (Casbin API)",
|
||||
"operationId": "ApiController.GetAllObjects",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "userId",
|
||||
"description": "user id like built-in/admin",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controllers.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/get-all-roles": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Enforcer API"
|
||||
],
|
||||
"description": "Get all roles for a user (Casbin API)",
|
||||
"operationId": "ApiController.GetAllRoles",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "userId",
|
||||
"description": "user id like built-in/admin",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controllers.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/get-app-login": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -3843,6 +4026,61 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/get-ticket": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Ticket API"
|
||||
],
|
||||
"description": "get ticket",
|
||||
"operationId": "ApiController.GetTicket",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "id",
|
||||
"description": "The id ( owner/name ) of the ticket",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/object.Ticket"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/get-tickets": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Ticket API"
|
||||
],
|
||||
"description": "get tickets",
|
||||
"operationId": "ApiController.GetTickets",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "owner",
|
||||
"description": "The owner of tickets",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/object.Ticket"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/get-token": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -4299,6 +4537,32 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/impersonation-user": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"User API"
|
||||
],
|
||||
"description": "set impersonation user for current admin session",
|
||||
"operationId": "ApiController.ImpersonateUser",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "formData",
|
||||
"name": "username",
|
||||
"description": "The username to impersonate (owner/name)",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controllers.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/invoice-payment": {
|
||||
"post": {
|
||||
"tags": [
|
||||
@@ -5219,8 +5483,16 @@
|
||||
"tags": [
|
||||
"Login API"
|
||||
],
|
||||
"description": "logout the current user from all applications",
|
||||
"description": "logout the current user from all applications or current session only",
|
||||
"operationId": "ApiController.SsoLogout",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "logoutAll",
|
||||
"description": "Whether to logout from all sessions. Accepted values: 'true', '1', or empty (default: true). Any other value means false.",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
@@ -5234,8 +5506,16 @@
|
||||
"tags": [
|
||||
"Login API"
|
||||
],
|
||||
"description": "logout the current user from all applications",
|
||||
"description": "logout the current user from all applications or current session only",
|
||||
"operationId": "ApiController.SsoLogout",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "logoutAll",
|
||||
"description": "Whether to logout from all sessions. Accepted values: 'true', '1', or empty (default: true). Any other value means false.",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
@@ -6082,6 +6362,41 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/update-ticket": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Ticket API"
|
||||
],
|
||||
"description": "update ticket",
|
||||
"operationId": "ApiController.UpdateTicket",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "id",
|
||||
"description": "The id ( owner/name ) of the ticket",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"in": "body",
|
||||
"name": "body",
|
||||
"description": "The details of the ticket",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/object.Ticket"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controllers.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/update-token": {
|
||||
"post": {
|
||||
"tags": [
|
||||
@@ -6564,14 +6879,18 @@
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"217748.\u003cnil\u003e.string": {
|
||||
"232967.\u003cnil\u003e.string": {
|
||||
"title": "string",
|
||||
"type": "object"
|
||||
},
|
||||
"217806.string.string": {
|
||||
"233025.string.string": {
|
||||
"title": "string",
|
||||
"type": "object"
|
||||
},
|
||||
"McpResponse": {
|
||||
"title": "McpResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"Response": {
|
||||
"title": "Response",
|
||||
"type": "object"
|
||||
@@ -6763,6 +7082,9 @@
|
||||
"regex": {
|
||||
"type": "string"
|
||||
},
|
||||
"tab": {
|
||||
"type": "string"
|
||||
},
|
||||
"viewRule": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -6814,6 +7136,33 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"object.Address": {
|
||||
"title": "Address",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"city": {
|
||||
"type": "string"
|
||||
},
|
||||
"line1": {
|
||||
"type": "string"
|
||||
},
|
||||
"line2": {
|
||||
"type": "string"
|
||||
},
|
||||
"region": {
|
||||
"type": "string"
|
||||
},
|
||||
"state": {
|
||||
"type": "string"
|
||||
},
|
||||
"tag": {
|
||||
"type": "string"
|
||||
},
|
||||
"zipCode": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"object.Application": {
|
||||
"title": "Application",
|
||||
"type": "object",
|
||||
@@ -6837,6 +7186,10 @@
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"cookieExpireInHours": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"createdTime": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -6870,6 +7223,9 @@
|
||||
"enablePassword": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"enableSamlAssertionSignature": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"enableSamlC14n10": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -7822,9 +8178,6 @@
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"endTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -7837,18 +8190,15 @@
|
||||
"payment": {
|
||||
"type": "string"
|
||||
},
|
||||
"planName": {
|
||||
"type": "string"
|
||||
},
|
||||
"price": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"pricingName": {
|
||||
"type": "string"
|
||||
},
|
||||
"productName": {
|
||||
"type": "string"
|
||||
"productInfos": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/object.ProductInfo"
|
||||
}
|
||||
},
|
||||
"products": {
|
||||
"type": "array",
|
||||
@@ -7856,10 +8206,10 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"startTime": {
|
||||
"state": {
|
||||
"type": "string"
|
||||
},
|
||||
"state": {
|
||||
"updateTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"user": {
|
||||
@@ -7877,6 +8227,9 @@
|
||||
"$ref": "#/definitions/object.AccountItem"
|
||||
}
|
||||
},
|
||||
"accountMenu": {
|
||||
"type": "string"
|
||||
},
|
||||
"balanceCredit": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
@@ -8090,9 +8443,6 @@
|
||||
"invoiceUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"isRecharge": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -8102,6 +8452,9 @@
|
||||
"order": {
|
||||
"type": "string"
|
||||
},
|
||||
"orderObj": {
|
||||
"$ref": "#/definitions/object.Order"
|
||||
},
|
||||
"outOrderId": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -8127,10 +8480,13 @@
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"productDisplayName": {
|
||||
"type": "string"
|
||||
"products": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"productName": {
|
||||
"productsDisplayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"provider": {
|
||||
@@ -8142,9 +8498,6 @@
|
||||
"successUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"tag": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -8402,6 +8755,47 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"object.ProductInfo": {
|
||||
"title": "ProductInfo",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"currency": {
|
||||
"type": "string"
|
||||
},
|
||||
"detail": {
|
||||
"type": "string"
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"image": {
|
||||
"type": "string"
|
||||
},
|
||||
"isRecharge": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"owner": {
|
||||
"type": "string"
|
||||
},
|
||||
"planName": {
|
||||
"type": "string"
|
||||
},
|
||||
"price": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"pricingName": {
|
||||
"type": "string"
|
||||
},
|
||||
"quantity": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
},
|
||||
"object.PrometheusInfo": {
|
||||
"title": "PrometheusInfo",
|
||||
"type": "object",
|
||||
@@ -8482,6 +8876,9 @@
|
||||
"emailRegex": {
|
||||
"type": "string"
|
||||
},
|
||||
"enablePkce": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"enableProxy": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -9024,6 +9421,63 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"object.Ticket": {
|
||||
"title": "Ticket",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"messages": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/object.TicketMessage"
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"owner": {
|
||||
"type": "string"
|
||||
},
|
||||
"state": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"updatedTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"user": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"object.TicketMessage": {
|
||||
"title": "TicketMessage",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"author": {
|
||||
"type": "string"
|
||||
},
|
||||
"isAdmin": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"timestamp": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"object.Token": {
|
||||
"title": "Token",
|
||||
"type": "object",
|
||||
@@ -9132,7 +9586,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"category": {
|
||||
"type": "string"
|
||||
"$ref": "#/definitions/object.TransactionCategory"
|
||||
},
|
||||
"createdTime": {
|
||||
"type": "string"
|
||||
@@ -9159,7 +9613,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"state": {
|
||||
"$ref": "#/definitions/pp.PaymentState"
|
||||
"type": "string"
|
||||
},
|
||||
"subtype": {
|
||||
"type": "string"
|
||||
@@ -9175,6 +9629,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"object.TransactionCategory": {
|
||||
"title": "TransactionCategory",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"TransactionCategoryPurchase = \"Purchase\"",
|
||||
"TransactionCategoryRecharge = \"Recharge\""
|
||||
],
|
||||
"example": "Purchase"
|
||||
},
|
||||
"object.User": {
|
||||
"title": "User",
|
||||
"type": "object",
|
||||
@@ -9194,6 +9657,12 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"addresses": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/object.Address"
|
||||
}
|
||||
},
|
||||
"adfs": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -9256,6 +9725,12 @@
|
||||
"box": {
|
||||
"type": "string"
|
||||
},
|
||||
"cart": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/object.ProductInfo"
|
||||
}
|
||||
},
|
||||
"casdoor": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -9569,10 +10044,10 @@
|
||||
"onedrive": {
|
||||
"type": "string"
|
||||
},
|
||||
"originalToken": {
|
||||
"originalRefreshToken": {
|
||||
"type": "string"
|
||||
},
|
||||
"originalRefreshToken": {
|
||||
"originalToken": {
|
||||
"type": "string"
|
||||
},
|
||||
"oura": {
|
||||
@@ -9828,7 +10303,7 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"aliases": {
|
||||
"$ref": "#/definitions/217748.\u003cnil\u003e.string"
|
||||
"$ref": "#/definitions/232967.\u003cnil\u003e.string"
|
||||
},
|
||||
"links": {
|
||||
"type": "array",
|
||||
@@ -9837,7 +10312,7 @@
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"$ref": "#/definitions/217806.string.string"
|
||||
"$ref": "#/definitions/233025.string.string"
|
||||
},
|
||||
"subject": {
|
||||
"type": "string"
|
||||
|
||||
@@ -548,6 +548,47 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/add-ticket:
|
||||
post:
|
||||
tags:
|
||||
- Ticket API
|
||||
description: add ticket
|
||||
operationId: ApiController.AddTicket
|
||||
parameters:
|
||||
- in: body
|
||||
name: body
|
||||
description: The details of the ticket
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/object.Ticket'
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/add-ticket-message:
|
||||
post:
|
||||
tags:
|
||||
- Ticket API
|
||||
description: add a message to a ticket
|
||||
operationId: ApiController.AddTicketMessage
|
||||
parameters:
|
||||
- in: query
|
||||
name: id
|
||||
description: The id ( owner/name ) of the ticket
|
||||
required: true
|
||||
type: string
|
||||
- in: body
|
||||
name: body
|
||||
description: The message to add
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/object.TicketMessage'
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/add-token:
|
||||
post:
|
||||
tags:
|
||||
@@ -1099,6 +1140,24 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/delete-ticket:
|
||||
post:
|
||||
tags:
|
||||
- Ticket API
|
||||
description: delete ticket
|
||||
operationId: ApiController.DeleteTicket
|
||||
parameters:
|
||||
- in: body
|
||||
name: body
|
||||
description: The details of the ticket
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/object.Ticket'
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/delete-token:
|
||||
post:
|
||||
tags:
|
||||
@@ -1218,6 +1277,17 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/exit-impersonation-user:
|
||||
post:
|
||||
tags:
|
||||
- User API
|
||||
description: clear impersonation info for current session
|
||||
operationId: ApiController.ExitImpersonateUser
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/faceid-signin-begin:
|
||||
get:
|
||||
tags:
|
||||
@@ -1287,6 +1357,54 @@ paths:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/object.Adapter'
|
||||
/api/get-all-actions:
|
||||
get:
|
||||
tags:
|
||||
- Enforcer API
|
||||
description: Get all actions for a user (Casbin API)
|
||||
operationId: ApiController.GetAllActions
|
||||
parameters:
|
||||
- in: query
|
||||
name: userId
|
||||
description: user id like built-in/admin
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/get-all-objects:
|
||||
get:
|
||||
tags:
|
||||
- Enforcer API
|
||||
description: Get all objects for a user (Casbin API)
|
||||
operationId: ApiController.GetAllObjects
|
||||
parameters:
|
||||
- in: query
|
||||
name: userId
|
||||
description: user id like built-in/admin
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/get-all-roles:
|
||||
get:
|
||||
tags:
|
||||
- Enforcer API
|
||||
description: Get all roles for a user (Casbin API)
|
||||
operationId: ApiController.GetAllRoles
|
||||
parameters:
|
||||
- in: query
|
||||
name: userId
|
||||
description: user id like built-in/admin
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/get-app-login:
|
||||
get:
|
||||
tags:
|
||||
@@ -2498,6 +2616,42 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/util.SystemInfo'
|
||||
/api/get-ticket:
|
||||
get:
|
||||
tags:
|
||||
- Ticket API
|
||||
description: get ticket
|
||||
operationId: ApiController.GetTicket
|
||||
parameters:
|
||||
- in: query
|
||||
name: id
|
||||
description: The id ( owner/name ) of the ticket
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.Ticket'
|
||||
/api/get-tickets:
|
||||
get:
|
||||
tags:
|
||||
- Ticket API
|
||||
description: get tickets
|
||||
operationId: ApiController.GetTickets
|
||||
parameters:
|
||||
- in: query
|
||||
name: owner
|
||||
description: The owner of tickets
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/object.Ticket'
|
||||
/api/get-token:
|
||||
get:
|
||||
tags:
|
||||
@@ -2797,6 +2951,23 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/impersonation-user:
|
||||
post:
|
||||
tags:
|
||||
- User API
|
||||
description: set impersonation user for current admin session
|
||||
operationId: ApiController.ImpersonateUser
|
||||
parameters:
|
||||
- in: formData
|
||||
name: username
|
||||
description: The username to impersonate (owner/name)
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/invoice-payment:
|
||||
post:
|
||||
tags:
|
||||
@@ -3406,8 +3577,13 @@ paths:
|
||||
get:
|
||||
tags:
|
||||
- Login API
|
||||
description: logout the current user from all applications
|
||||
description: logout the current user from all applications or current session only
|
||||
operationId: ApiController.SsoLogout
|
||||
parameters:
|
||||
- in: query
|
||||
name: logoutAll
|
||||
description: 'Whether to logout from all sessions. Accepted values: ''true'', ''1'', or empty (default: true). Any other value means false.'
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
@@ -3416,8 +3592,13 @@ paths:
|
||||
post:
|
||||
tags:
|
||||
- Login API
|
||||
description: logout the current user from all applications
|
||||
description: logout the current user from all applications or current session only
|
||||
operationId: ApiController.SsoLogout
|
||||
parameters:
|
||||
- in: query
|
||||
name: logoutAll
|
||||
description: 'Whether to logout from all sessions. Accepted values: ''true'', ''1'', or empty (default: true). Any other value means false.'
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
@@ -3971,6 +4152,29 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/update-ticket:
|
||||
post:
|
||||
tags:
|
||||
- Ticket API
|
||||
description: update ticket
|
||||
operationId: ApiController.UpdateTicket
|
||||
parameters:
|
||||
- in: query
|
||||
name: id
|
||||
description: The id ( owner/name ) of the ticket
|
||||
required: true
|
||||
type: string
|
||||
- in: body
|
||||
name: body
|
||||
description: The details of the ticket
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/object.Ticket'
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/update-token:
|
||||
post:
|
||||
tags:
|
||||
@@ -4286,12 +4490,15 @@ paths:
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
definitions:
|
||||
217748.<nil>.string:
|
||||
232967.<nil>.string:
|
||||
title: string
|
||||
type: object
|
||||
217806.string.string:
|
||||
233025.string.string:
|
||||
title: string
|
||||
type: object
|
||||
McpResponse:
|
||||
title: McpResponse
|
||||
type: object
|
||||
Response:
|
||||
title: Response
|
||||
type: object
|
||||
@@ -4423,6 +4630,8 @@ definitions:
|
||||
type: string
|
||||
regex:
|
||||
type: string
|
||||
tab:
|
||||
type: string
|
||||
viewRule:
|
||||
type: string
|
||||
visible:
|
||||
@@ -4456,6 +4665,24 @@ definitions:
|
||||
type: boolean
|
||||
user:
|
||||
type: string
|
||||
object.Address:
|
||||
title: Address
|
||||
type: object
|
||||
properties:
|
||||
city:
|
||||
type: string
|
||||
line1:
|
||||
type: string
|
||||
line2:
|
||||
type: string
|
||||
region:
|
||||
type: string
|
||||
state:
|
||||
type: string
|
||||
tag:
|
||||
type: string
|
||||
zipCode:
|
||||
type: string
|
||||
object.Application:
|
||||
title: Application
|
||||
type: object
|
||||
@@ -4473,6 +4700,9 @@ definitions:
|
||||
codeResendTimeout:
|
||||
type: integer
|
||||
format: int64
|
||||
cookieExpireInHours:
|
||||
type: integer
|
||||
format: int64
|
||||
createdTime:
|
||||
type: string
|
||||
defaultGroup:
|
||||
@@ -4495,6 +4725,8 @@ definitions:
|
||||
type: boolean
|
||||
enablePassword:
|
||||
type: boolean
|
||||
enableSamlAssertionSignature:
|
||||
type: boolean
|
||||
enableSamlC14n10:
|
||||
type: boolean
|
||||
enableSamlCompress:
|
||||
@@ -5136,8 +5368,6 @@ definitions:
|
||||
type: string
|
||||
displayName:
|
||||
type: string
|
||||
endTime:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
name:
|
||||
@@ -5146,23 +5376,21 @@ definitions:
|
||||
type: string
|
||||
payment:
|
||||
type: string
|
||||
planName:
|
||||
type: string
|
||||
price:
|
||||
type: number
|
||||
format: double
|
||||
pricingName:
|
||||
type: string
|
||||
productName:
|
||||
type: string
|
||||
productInfos:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/object.ProductInfo'
|
||||
products:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
startTime:
|
||||
type: string
|
||||
state:
|
||||
type: string
|
||||
updateTime:
|
||||
type: string
|
||||
user:
|
||||
type: string
|
||||
object.Organization:
|
||||
@@ -5173,6 +5401,8 @@ definitions:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/object.AccountItem'
|
||||
accountMenu:
|
||||
type: string
|
||||
balanceCredit:
|
||||
type: number
|
||||
format: double
|
||||
@@ -5317,14 +5547,14 @@ definitions:
|
||||
type: string
|
||||
invoiceUrl:
|
||||
type: string
|
||||
isRecharge:
|
||||
type: boolean
|
||||
message:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
order:
|
||||
type: string
|
||||
orderObj:
|
||||
$ref: '#/definitions/object.Order'
|
||||
outOrderId:
|
||||
type: string
|
||||
owner:
|
||||
@@ -5342,9 +5572,11 @@ definitions:
|
||||
price:
|
||||
type: number
|
||||
format: double
|
||||
productDisplayName:
|
||||
type: string
|
||||
productName:
|
||||
products:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
productsDisplayName:
|
||||
type: string
|
||||
provider:
|
||||
type: string
|
||||
@@ -5352,8 +5584,6 @@ definitions:
|
||||
$ref: '#/definitions/pp.PaymentState'
|
||||
successUrl:
|
||||
type: string
|
||||
tag:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
user:
|
||||
@@ -5526,6 +5756,34 @@ definitions:
|
||||
type: string
|
||||
tag:
|
||||
type: string
|
||||
object.ProductInfo:
|
||||
title: ProductInfo
|
||||
type: object
|
||||
properties:
|
||||
currency:
|
||||
type: string
|
||||
detail:
|
||||
type: string
|
||||
displayName:
|
||||
type: string
|
||||
image:
|
||||
type: string
|
||||
isRecharge:
|
||||
type: boolean
|
||||
name:
|
||||
type: string
|
||||
owner:
|
||||
type: string
|
||||
planName:
|
||||
type: string
|
||||
price:
|
||||
type: number
|
||||
format: double
|
||||
pricingName:
|
||||
type: string
|
||||
quantity:
|
||||
type: integer
|
||||
format: int64
|
||||
object.PrometheusInfo:
|
||||
title: PrometheusInfo
|
||||
type: object
|
||||
@@ -5581,6 +5839,8 @@ definitions:
|
||||
type: string
|
||||
emailRegex:
|
||||
type: string
|
||||
enablePkce:
|
||||
type: boolean
|
||||
enableProxy:
|
||||
type: boolean
|
||||
enableSignAuthnRequest:
|
||||
@@ -5945,6 +6205,44 @@ definitions:
|
||||
type: boolean
|
||||
themeType:
|
||||
type: string
|
||||
object.Ticket:
|
||||
title: Ticket
|
||||
type: object
|
||||
properties:
|
||||
content:
|
||||
type: string
|
||||
createdTime:
|
||||
type: string
|
||||
displayName:
|
||||
type: string
|
||||
messages:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/object.TicketMessage'
|
||||
name:
|
||||
type: string
|
||||
owner:
|
||||
type: string
|
||||
state:
|
||||
type: string
|
||||
title:
|
||||
type: string
|
||||
updatedTime:
|
||||
type: string
|
||||
user:
|
||||
type: string
|
||||
object.TicketMessage:
|
||||
title: TicketMessage
|
||||
type: object
|
||||
properties:
|
||||
author:
|
||||
type: string
|
||||
isAdmin:
|
||||
type: boolean
|
||||
text:
|
||||
type: string
|
||||
timestamp:
|
||||
type: string
|
||||
object.Token:
|
||||
title: Token
|
||||
type: object
|
||||
@@ -6020,7 +6318,7 @@ definitions:
|
||||
application:
|
||||
type: string
|
||||
category:
|
||||
type: string
|
||||
$ref: '#/definitions/object.TransactionCategory'
|
||||
createdTime:
|
||||
type: string
|
||||
currency:
|
||||
@@ -6038,7 +6336,7 @@ definitions:
|
||||
provider:
|
||||
type: string
|
||||
state:
|
||||
$ref: '#/definitions/pp.PaymentState'
|
||||
type: string
|
||||
subtype:
|
||||
type: string
|
||||
tag:
|
||||
@@ -6047,6 +6345,13 @@ definitions:
|
||||
type: string
|
||||
user:
|
||||
type: string
|
||||
object.TransactionCategory:
|
||||
title: TransactionCategory
|
||||
type: string
|
||||
enum:
|
||||
- TransactionCategoryPurchase = "Purchase"
|
||||
- TransactionCategoryRecharge = "Recharge"
|
||||
example: Purchase
|
||||
object.User:
|
||||
title: User
|
||||
type: object
|
||||
@@ -6061,6 +6366,10 @@ definitions:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
addresses:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/object.Address'
|
||||
adfs:
|
||||
type: string
|
||||
affiliation:
|
||||
@@ -6103,6 +6412,10 @@ definitions:
|
||||
type: string
|
||||
box:
|
||||
type: string
|
||||
cart:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/object.ProductInfo'
|
||||
casdoor:
|
||||
type: string
|
||||
cloudfoundry:
|
||||
@@ -6312,10 +6625,10 @@ definitions:
|
||||
type: string
|
||||
onedrive:
|
||||
type: string
|
||||
originalToken:
|
||||
type: string
|
||||
originalRefreshToken:
|
||||
type: string
|
||||
originalToken:
|
||||
type: string
|
||||
oura:
|
||||
type: string
|
||||
owner:
|
||||
@@ -6486,13 +6799,13 @@ definitions:
|
||||
type: object
|
||||
properties:
|
||||
aliases:
|
||||
$ref: '#/definitions/217748.<nil>.string'
|
||||
$ref: '#/definitions/232967.<nil>.string'
|
||||
links:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/object.WebFingerLink'
|
||||
properties:
|
||||
$ref: '#/definitions/217806.string.string'
|
||||
$ref: '#/definitions/233025.string.string'
|
||||
subject:
|
||||
type: string
|
||||
object.WebFingerLink:
|
||||
|
||||
@@ -239,26 +239,6 @@ class OrderEditPage extends React.Component {
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{i18next.t("order:Start time")}:
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.order.startTime} onChange={e => {
|
||||
this.updateOrderField("startTime", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{i18next.t("order:End time")}:
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.order.endTime} onChange={e => {
|
||||
this.updateOrderField("endTime", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
import React from "react";
|
||||
import {Link} from "react-router-dom";
|
||||
import {Button, List, Table, Tooltip} from "antd";
|
||||
import {Button, Col, List, Row, Table, Tooltip} from "antd";
|
||||
import moment from "moment";
|
||||
import * as Setting from "./Setting";
|
||||
import * as OrderBackend from "./backend/OrderBackend";
|
||||
@@ -37,8 +37,6 @@ class OrderListPage extends BaseListPage {
|
||||
payment: "",
|
||||
state: "Created",
|
||||
message: "",
|
||||
startTime: moment().format(),
|
||||
endTime: "",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -159,21 +157,31 @@ class OrderListPage extends BaseListPage {
|
||||
paddingBottom: 8,
|
||||
}}
|
||||
renderItem={(productInfo, i) => {
|
||||
const price = productInfo.price * (productInfo.quantity || 1);
|
||||
const price = productInfo.price || 0;
|
||||
const number = productInfo.quantity || 1;
|
||||
const currency = record.currency || "USD";
|
||||
const productName = productInfo.displayName || productInfo.name;
|
||||
return (
|
||||
<List.Item>
|
||||
<div style={{display: "inline"}}>
|
||||
<Tooltip placement="topLeft" title={i18next.t("general:Edit")}>
|
||||
<Button style={{marginRight: "5px"}} icon={<EditOutlined />} size="small" onClick={() => Setting.goToLinkSoft(this, `/products/${record.owner}/${productInfo.name}`)} />
|
||||
</Tooltip>
|
||||
<Link to={`/products/${record.owner}/${productInfo.name}`}>
|
||||
{productInfo.displayName || productInfo.name}
|
||||
</Link>
|
||||
<span style={{marginLeft: "8px", color: "#666"}}>
|
||||
{Setting.getPriceDisplay(price, currency)}
|
||||
</span>
|
||||
</div>
|
||||
<Row style={{width: "100%"}} wrap={false} gutter={[12, 0]}>
|
||||
<Col flex="auto" style={{minWidth: 0}}>
|
||||
<div style={{display: "flex", alignItems: "center", minWidth: 0}}>
|
||||
<Tooltip placement="topLeft" title={i18next.t("general:Edit")}>
|
||||
<Button style={{marginRight: "5px"}} icon={<EditOutlined />} size="small" onClick={() => Setting.goToLinkSoft(this, `/products/${record.owner}/${productInfo.name}`)} />
|
||||
</Tooltip>
|
||||
<Tooltip placement="topLeft" title={productName}>
|
||||
<Link to={`/products/${record.owner}/${productInfo.name}`} style={{display: "inline-block", maxWidth: "100%", minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap"}}>
|
||||
{productName}
|
||||
</Link>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Col>
|
||||
<Col flex="none" style={{whiteSpace: "nowrap"}}>
|
||||
<span style={{color: "#666"}}>
|
||||
{Setting.getCurrencySymbol(currency)}{price} ({Setting.getCurrencyText(currency)}) × {number}
|
||||
</span>
|
||||
</Col>
|
||||
</Row>
|
||||
</List.Item>
|
||||
);
|
||||
}}
|
||||
@@ -229,29 +237,6 @@ class OrderListPage extends BaseListPage {
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("state"),
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Start time"),
|
||||
dataIndex: "startTime",
|
||||
key: "startTime",
|
||||
width: "160px",
|
||||
sorter: true,
|
||||
render: (text, record, index) => {
|
||||
return Setting.getFormattedDate(text);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:End time"),
|
||||
dataIndex: "endTime",
|
||||
key: "endTime",
|
||||
width: "160px",
|
||||
sorter: true,
|
||||
render: (text, record, index) => {
|
||||
if (text === "") {
|
||||
return "(empty)";
|
||||
}
|
||||
return Setting.getFormattedDate(text);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Action"),
|
||||
dataIndex: "",
|
||||
|
||||
@@ -267,6 +267,17 @@ class OrderPayPage extends React.Component {
|
||||
render() {
|
||||
const {order, productInfos} = this.state;
|
||||
|
||||
const updateTime = order?.updateTime || "";
|
||||
const state = order?.state || "";
|
||||
const updateTimeMap = {
|
||||
Paid: i18next.t("order:Payment time"),
|
||||
Canceled: i18next.t("order:Cancel time"),
|
||||
PaymentFailed: i18next.t("order:Payment failed time"),
|
||||
Timeout: i18next.t("order:Timeout time"),
|
||||
};
|
||||
const updateTimeLabel = updateTimeMap[state] || i18next.t("general:Updated time");
|
||||
const shouldShowUpdateTime = state !== "Created" && updateTime !== "";
|
||||
|
||||
if (!order || !productInfos) {
|
||||
return null;
|
||||
}
|
||||
@@ -291,6 +302,13 @@ class OrderPayPage extends React.Component {
|
||||
{Setting.getFormattedDate(order.createdTime)}
|
||||
</span>
|
||||
</Descriptions.Item>
|
||||
{shouldShowUpdateTime && (
|
||||
<Descriptions.Item label={updateTimeLabel}>
|
||||
<span style={{fontSize: 16}}>
|
||||
{Setting.getFormattedDate(updateTime)}
|
||||
</span>
|
||||
</Descriptions.Item>
|
||||
)}
|
||||
<Descriptions.Item label={i18next.t("general:User")}>
|
||||
<span style={{fontSize: 16}}>
|
||||
{order.user}
|
||||
|
||||
@@ -1173,7 +1173,7 @@ class ProviderEditPage extends React.Component {
|
||||
</Col>
|
||||
</Row>
|
||||
) : null}
|
||||
{["AWS S3", "Tencent Cloud COS", "Qiniu Cloud Kodo", "Casdoor", "CUCloud OSS", "MinIO", "CUCloud"].includes(this.state.provider.type) ? (
|
||||
{["AWS S3", "Tencent Cloud COS", "Qiniu Cloud Kodo", "Casdoor", "CUCloud OSS", "MinIO", "CUCloud", "Alibaba Cloud PNVS SMS"].includes(this.state.provider.type) ? (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={2}>
|
||||
{["Casdoor"].includes(this.state.provider.type) ?
|
||||
|
||||
@@ -1293,6 +1293,7 @@ export function getProviderTypeOptions(category) {
|
||||
return (
|
||||
[
|
||||
{id: "Aliyun SMS", name: "Alibaba Cloud SMS"},
|
||||
{id: "Alibaba Cloud PNVS SMS", name: "Alibaba Cloud PNVS SMS"},
|
||||
{id: "Amazon SNS", name: "Amazon SNS"},
|
||||
{id: "Azure ACS", name: "Azure ACS"},
|
||||
{id: "Custom HTTP SMS", name: "Custom HTTP SMS"},
|
||||
|
||||
Reference in New Issue
Block a user