mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-22 21:33:36 +08:00
Compare commits
53 Commits
Author | SHA1 | Date | |
---|---|---|---|
34151c0095 | |||
c7cea331e2 | |||
8ede4993af | |||
d04dd33d8b | |||
8cb21253f6 | |||
7fc697b711 | |||
80e6e7f0a7 | |||
d29fc88d68 | |||
225e9cf70a | |||
c57c6e37dd | |||
4d860525bf | |||
a64263f812 | |||
95ab2472ce | |||
54e4747dbc | |||
2389d47c34 | |||
9c4f0f042e | |||
e25e210b06 | |||
df61a536c1 | |||
47da3cdaa0 | |||
8d246f2d98 | |||
44cd55e55f | |||
6b42d35223 | |||
c84150cede | |||
de2689ac39 | |||
88c0856d17 | |||
319031da28 | |||
d20f3eb039 | |||
3e13e61d8f | |||
1260354b36 | |||
af79fdedf2 | |||
02333f2f0c | |||
79bd58e0e6 | |||
de73ff0e60 | |||
a9d662f1bd | |||
65dcbd2236 | |||
6455734807 | |||
2eefeaffa7 | |||
04eaad1c80 | |||
9f084a0799 | |||
293b9f1036 | |||
437376c472 | |||
cc528c5d8c | |||
54e2055ffb | |||
983a30a2e0 | |||
37d0157d41 | |||
d4dc236770 | |||
596742d782 | |||
ce921c00cd | |||
3830e443b0 | |||
9092cad631 | |||
0b5ecca5c8 | |||
3d9b305bbb | |||
0217e359e7 |
4
.gitignore
vendored
4
.gitignore
vendored
@ -29,4 +29,6 @@ lastupdate.tmp
|
|||||||
commentsRouter*.go
|
commentsRouter*.go
|
||||||
|
|
||||||
# ignore build result
|
# ignore build result
|
||||||
casdoor
|
casdoor
|
||||||
|
server_linux_arm64
|
||||||
|
server_linux_amd64
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
FROM node:16.13.0 AS FRONT
|
FROM node:16.18.0 AS FRONT
|
||||||
WORKDIR /web
|
WORKDIR /web
|
||||||
COPY ./web .
|
COPY ./web .
|
||||||
RUN yarn config set registry https://registry.npmmirror.com
|
RUN yarn config set registry https://registry.npmmirror.com
|
||||||
RUN yarn install --frozen-lockfile --network-timeout 1000000 && yarn run build
|
RUN yarn install --frozen-lockfile --network-timeout 1000000 && yarn run build
|
||||||
|
|
||||||
|
|
||||||
FROM golang:1.17.5 AS BACK
|
FROM golang:1.19.9 AS BACK
|
||||||
WORKDIR /go/src/casdoor
|
WORKDIR /go/src/casdoor
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN ./build.sh
|
RUN ./build.sh
|
||||||
|
@ -15,13 +15,13 @@
|
|||||||
package authz
|
package authz
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/casbin/casbin/v2"
|
"github.com/casbin/casbin/v2"
|
||||||
"github.com/casbin/casbin/v2/model"
|
"github.com/casbin/casbin/v2/model"
|
||||||
"github.com/casdoor/casdoor/conf"
|
"github.com/casdoor/casdoor/conf"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
xormadapter "github.com/casdoor/xorm-adapter/v3"
|
xormadapter "github.com/casdoor/xorm-adapter/v3"
|
||||||
stringadapter "github.com/qiangmzsx/string-adapter/v2"
|
stringadapter "github.com/qiangmzsx/string-adapter/v2"
|
||||||
)
|
)
|
||||||
@ -88,6 +88,7 @@ p, *, *, GET, /api/logout, *, *
|
|||||||
p, *, *, GET, /api/get-account, *, *
|
p, *, *, GET, /api/get-account, *, *
|
||||||
p, *, *, GET, /api/userinfo, *, *
|
p, *, *, GET, /api/userinfo, *, *
|
||||||
p, *, *, GET, /api/user, *, *
|
p, *, *, GET, /api/user, *, *
|
||||||
|
p, *, *, GET, /api/health, *, *
|
||||||
p, *, *, POST, /api/webhook, *, *
|
p, *, *, POST, /api/webhook, *, *
|
||||||
p, *, *, GET, /api/get-webhook-event, *, *
|
p, *, *, GET, /api/get-webhook-event, *, *
|
||||||
p, *, *, GET, /api/get-captcha-status, *, *
|
p, *, *, GET, /api/get-captcha-status, *, *
|
||||||
@ -123,6 +124,10 @@ p, *, *, GET, /api/get-release, *, *
|
|||||||
p, *, *, GET, /api/get-default-application, *, *
|
p, *, *, GET, /api/get-default-application, *, *
|
||||||
p, *, *, GET, /api/get-prometheus-info, *, *
|
p, *, *, GET, /api/get-prometheus-info, *, *
|
||||||
p, *, *, *, /api/metrics, *, *
|
p, *, *, *, /api/metrics, *, *
|
||||||
|
p, *, *, GET, /api/get-subscriptions, *, *
|
||||||
|
p, *, *, GET, /api/get-pricing, *, *
|
||||||
|
p, *, *, GET, /api/get-plan, *, *
|
||||||
|
p, *, *, GET, /api/get-organization-names, *, *
|
||||||
`
|
`
|
||||||
|
|
||||||
sa := stringadapter.NewAdapter(ruleText)
|
sa := stringadapter.NewAdapter(ruleText)
|
||||||
@ -149,8 +154,7 @@ func IsAllowed(subOwner string, subName string, method string, urlPath string, o
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
userId := fmt.Sprintf("%s/%s", subOwner, subName)
|
user := object.GetUser(util.GetId(subOwner, subName))
|
||||||
user := object.GetUser(userId)
|
|
||||||
if user != nil && user.IsAdmin && (subOwner == objOwner || (objOwner == "admin")) {
|
if user != nil && user.IsAdmin && (subOwner == objOwner || (objOwner == "admin")) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -20,5 +20,4 @@ staticBaseUrl = "https://cdn.casbin.org"
|
|||||||
isDemoMode = false
|
isDemoMode = false
|
||||||
batchSize = 100
|
batchSize = 100
|
||||||
ldapServerPort = 389
|
ldapServerPort = 389
|
||||||
languages = en,zh,es,fr,de,id,ja,ko,ru,vi
|
|
||||||
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}
|
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}
|
||||||
|
@ -112,13 +112,8 @@ func GetLanguage(language string) string {
|
|||||||
|
|
||||||
if len(language) < 2 {
|
if len(language) < 2 {
|
||||||
return "en"
|
return "en"
|
||||||
}
|
|
||||||
|
|
||||||
language = language[0:2]
|
|
||||||
if strings.Contains(GetConfigString("languages"), language) {
|
|
||||||
return language
|
|
||||||
} else {
|
} else {
|
||||||
return "en"
|
return language[0:2]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ func (c *ApiController) Signup() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", authForm.Organization))
|
organization := object.GetOrganization(util.GetId("admin", authForm.Organization))
|
||||||
msg := object.CheckUserSignup(application, organization, &authForm, c.GetAcceptLanguage())
|
msg := object.CheckUserSignup(application, organization, &authForm, c.GetAcceptLanguage())
|
||||||
if msg != "" {
|
if msg != "" {
|
||||||
c.ResponseError(msg)
|
c.ResponseError(msg)
|
||||||
@ -126,7 +126,7 @@ func (c *ApiController) Signup() {
|
|||||||
username = id
|
username = id
|
||||||
}
|
}
|
||||||
|
|
||||||
initScore, err := getInitScore(organization)
|
initScore, err := organization.GetInitScore()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(fmt.Errorf(c.T("account:Get init score failed, error: %w"), err).Error())
|
c.ResponseError(fmt.Errorf(c.T("account:Get init score failed, error: %w"), err).Error())
|
||||||
return
|
return
|
||||||
@ -189,6 +189,11 @@ func (c *ApiController) Signup() {
|
|||||||
object.DisableVerificationCode(authForm.Email)
|
object.DisableVerificationCode(authForm.Email)
|
||||||
object.DisableVerificationCode(checkPhone)
|
object.DisableVerificationCode(checkPhone)
|
||||||
|
|
||||||
|
isSignupFromPricing := authForm.Plan != "" && authForm.Pricing != ""
|
||||||
|
if isSignupFromPricing {
|
||||||
|
object.Subscribe(organization.Name, user.Name, authForm.Plan, authForm.Pricing)
|
||||||
|
}
|
||||||
|
|
||||||
record := object.NewRecord(c.Ctx)
|
record := object.NewRecord(c.Ctx)
|
||||||
record.Organization = application.Organization
|
record.Organization = application.Organization
|
||||||
record.User = user.Name
|
record.User = user.Name
|
||||||
|
@ -118,8 +118,7 @@ func (c *ApiController) GetOrganizationApplications() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if limit == "" || page == "" {
|
if limit == "" || page == "" {
|
||||||
var applications []*object.Application
|
applications := object.GetOrganizationApplications(owner, organization)
|
||||||
applications = object.GetOrganizationApplications(owner, organization)
|
|
||||||
c.Data["json"] = object.GetMaskedApplications(applications, userId)
|
c.Data["json"] = object.GetMaskedApplications(applications, userId)
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
} else {
|
} else {
|
||||||
|
@ -312,6 +312,11 @@ func (c *ApiController) Login() {
|
|||||||
|
|
||||||
resp = c.HandleLoggedIn(application, user, &authForm)
|
resp = c.HandleLoggedIn(application, user, &authForm)
|
||||||
|
|
||||||
|
organization := object.GetOrganizationByUser(user)
|
||||||
|
if user != nil && organization.HasRequiredMfa() && !user.IsMfaEnabled() {
|
||||||
|
resp.Msg = object.RequiredMfa
|
||||||
|
}
|
||||||
|
|
||||||
record := object.NewRecord(c.Ctx)
|
record := object.NewRecord(c.Ctx)
|
||||||
record.Organization = application.Organization
|
record.Organization = application.Organization
|
||||||
record.User = user.Name
|
record.User = user.Name
|
||||||
@ -330,7 +335,7 @@ func (c *ApiController) Login() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", application.Organization))
|
organization := object.GetOrganization(util.GetId("admin", application.Organization))
|
||||||
provider := object.GetProvider(util.GetId("admin", authForm.Provider))
|
provider := object.GetProvider(util.GetId("admin", authForm.Provider))
|
||||||
providerItem := application.GetProviderItem(provider.Name)
|
providerItem := application.GetProviderItem(provider.Name)
|
||||||
if !providerItem.IsProviderVisible() {
|
if !providerItem.IsProviderVisible() {
|
||||||
@ -391,7 +396,7 @@ func (c *ApiController) Login() {
|
|||||||
if authForm.Method == "signup" {
|
if authForm.Method == "signup" {
|
||||||
user := &object.User{}
|
user := &object.User{}
|
||||||
if provider.Category == "SAML" {
|
if provider.Category == "SAML" {
|
||||||
user = object.GetUser(fmt.Sprintf("%s/%s", application.Organization, userInfo.Id))
|
user = object.GetUser(util.GetId(application.Organization, userInfo.Id))
|
||||||
} else if provider.Category == "OAuth" {
|
} else if provider.Category == "OAuth" {
|
||||||
user = object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
|
user = object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
|
||||||
}
|
}
|
||||||
@ -411,24 +416,31 @@ func (c *ApiController) Login() {
|
|||||||
util.SafeGoroutine(func() { object.AddRecord(record) })
|
util.SafeGoroutine(func() { object.AddRecord(record) })
|
||||||
} else if provider.Category == "OAuth" {
|
} else if provider.Category == "OAuth" {
|
||||||
// Sign up via OAuth
|
// Sign up via OAuth
|
||||||
if !application.EnableSignUp {
|
|
||||||
c.ResponseError(fmt.Sprintf(c.T("auth: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"), provider.Type, userInfo.Username, userInfo.DisplayName))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !providerItem.CanSignUp {
|
|
||||||
c.ResponseError(fmt.Sprintf(c.T("auth: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"), provider.Type, userInfo.Username, userInfo.DisplayName, provider.Type))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if application.EnableLinkWithEmail {
|
if application.EnableLinkWithEmail {
|
||||||
// find user that has the same email
|
if userInfo.Email != "" {
|
||||||
user = object.GetUserByField(application.Organization, "email", userInfo.Email)
|
// Find existing user with Email
|
||||||
|
user = object.GetUserByField(application.Organization, "email", userInfo.Email)
|
||||||
|
}
|
||||||
|
|
||||||
|
if user == nil && userInfo.Phone != "" {
|
||||||
|
// Find existing user with phone number
|
||||||
|
user = object.GetUserByField(application.Organization, "phone", userInfo.Phone)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if user == nil || user.IsDeleted {
|
if user == nil || user.IsDeleted {
|
||||||
|
if !application.EnableSignUp {
|
||||||
|
c.ResponseError(fmt.Sprintf(c.T("auth: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"), provider.Type, userInfo.Username, userInfo.DisplayName))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !providerItem.CanSignUp {
|
||||||
|
c.ResponseError(fmt.Sprintf(c.T("auth: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"), provider.Type, userInfo.Username, userInfo.DisplayName, provider.Type))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Handle username conflicts
|
// Handle username conflicts
|
||||||
tmpUser := object.GetUser(fmt.Sprintf("%s/%s", application.Organization, userInfo.Username))
|
tmpUser := object.GetUser(util.GetId(application.Organization, userInfo.Username))
|
||||||
if tmpUser != nil {
|
if tmpUser != nil {
|
||||||
uid, err := uuid.NewRandom()
|
uid, err := uuid.NewRandom()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -442,7 +454,7 @@ func (c *ApiController) Login() {
|
|||||||
|
|
||||||
properties := map[string]string{}
|
properties := map[string]string{}
|
||||||
properties["no"] = strconv.Itoa(object.GetUserCount(application.Organization, "", "") + 2)
|
properties["no"] = strconv.Itoa(object.GetUserCount(application.Organization, "", "") + 2)
|
||||||
initScore, err := getInitScore(organization)
|
initScore, err := organization.GetInitScore()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(fmt.Errorf(c.T("account:Get init score failed, error: %w"), err).Error())
|
c.ResponseError(fmt.Errorf(c.T("account:Get init score failed, error: %w"), err).Error())
|
||||||
return
|
return
|
||||||
|
@ -48,7 +48,7 @@ func (c *ApiController) IsGlobalAdmin() bool {
|
|||||||
|
|
||||||
func (c *ApiController) IsAdmin() bool {
|
func (c *ApiController) IsAdmin() bool {
|
||||||
isGlobalAdmin, user := c.isGlobalAdmin()
|
isGlobalAdmin, user := c.isGlobalAdmin()
|
||||||
if user == nil {
|
if !isGlobalAdmin && user == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,8 +30,7 @@ import (
|
|||||||
// @Success 200 {array} object.Chat The Response object
|
// @Success 200 {array} object.Chat The Response object
|
||||||
// @router /get-chats [get]
|
// @router /get-chats [get]
|
||||||
func (c *ApiController) GetChats() {
|
func (c *ApiController) GetChats() {
|
||||||
owner := c.Input().Get("owner")
|
owner := "admin"
|
||||||
owner = "admin"
|
|
||||||
limit := c.Input().Get("pageSize")
|
limit := c.Input().Get("pageSize")
|
||||||
page := c.Input().Get("p")
|
page := c.Input().Get("p")
|
||||||
field := c.Input().Get("field")
|
field := c.Input().Get("field")
|
||||||
|
@ -24,6 +24,7 @@ import (
|
|||||||
func (c *ApiController) Enforce() {
|
func (c *ApiController) Enforce() {
|
||||||
permissionId := c.Input().Get("permissionId")
|
permissionId := c.Input().Get("permissionId")
|
||||||
modelId := c.Input().Get("modelId")
|
modelId := c.Input().Get("modelId")
|
||||||
|
resourceId := c.Input().Get("resourceId")
|
||||||
|
|
||||||
var request object.CasbinRequest
|
var request object.CasbinRequest
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &request)
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &request)
|
||||||
@ -35,17 +36,24 @@ func (c *ApiController) Enforce() {
|
|||||||
if permissionId != "" {
|
if permissionId != "" {
|
||||||
c.Data["json"] = object.Enforce(permissionId, &request)
|
c.Data["json"] = object.Enforce(permissionId, &request)
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
} else {
|
return
|
||||||
owner, modelName := util.GetOwnerAndNameFromId(modelId)
|
|
||||||
permissions := object.GetPermissionsByModel(owner, modelName)
|
|
||||||
|
|
||||||
res := []bool{}
|
|
||||||
for _, permission := range permissions {
|
|
||||||
res = append(res, object.Enforce(permission.GetId(), &request))
|
|
||||||
}
|
|
||||||
c.Data["json"] = res
|
|
||||||
c.ServeJSON()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
permissions := make([]*object.Permission, 0)
|
||||||
|
res := []bool{}
|
||||||
|
|
||||||
|
if modelId != "" {
|
||||||
|
owner, modelName := util.GetOwnerAndNameFromId(modelId)
|
||||||
|
permissions = object.GetPermissionsByModel(owner, modelName)
|
||||||
|
} else {
|
||||||
|
permissions = object.GetPermissionsByResource(resourceId)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, permission := range permissions {
|
||||||
|
res = append(res, object.Enforce(permission.GetId(), &request))
|
||||||
|
}
|
||||||
|
c.Data["json"] = res
|
||||||
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ApiController) BatchEnforce() {
|
func (c *ApiController) BatchEnforce() {
|
||||||
|
@ -23,7 +23,8 @@ import (
|
|||||||
|
|
||||||
type LdapResp struct {
|
type LdapResp struct {
|
||||||
// Groups []LdapRespGroup `json:"groups"`
|
// Groups []LdapRespGroup `json:"groups"`
|
||||||
Users []object.LdapRespUser `json:"users"`
|
Users []object.LdapUser `json:"users"`
|
||||||
|
ExistUuids []string `json:"existUuids"`
|
||||||
}
|
}
|
||||||
|
|
||||||
//type LdapRespGroup struct {
|
//type LdapRespGroup struct {
|
||||||
@ -32,8 +33,8 @@ type LdapResp struct {
|
|||||||
//}
|
//}
|
||||||
|
|
||||||
type LdapSyncResp struct {
|
type LdapSyncResp struct {
|
||||||
Exist []object.LdapRespUser `json:"exist"`
|
Exist []object.LdapUser `json:"exist"`
|
||||||
Failed []object.LdapRespUser `json:"failed"`
|
Failed []object.LdapUser `json:"failed"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLdapUsers
|
// GetLdapUsers
|
||||||
@ -71,27 +72,17 @@ func (c *ApiController) GetLdapUsers() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var resp LdapResp
|
|
||||||
uuids := make([]string, len(users))
|
uuids := make([]string, len(users))
|
||||||
for _, user := range users {
|
for i, user := range users {
|
||||||
resp.Users = append(resp.Users, object.LdapRespUser{
|
uuids[i] = user.GetLdapUuid()
|
||||||
UidNumber: user.UidNumber,
|
|
||||||
Uid: user.Uid,
|
|
||||||
Cn: user.Cn,
|
|
||||||
GroupId: user.GidNumber,
|
|
||||||
// GroupName: groupsMap[user.GidNumber].Cn,
|
|
||||||
Uuid: user.Uuid,
|
|
||||||
DisplayName: user.DisplayName,
|
|
||||||
Email: util.GetMaxLenStr(user.Mail, user.Email, user.EmailAddress),
|
|
||||||
Phone: util.GetMaxLenStr(user.TelephoneNumber, user.Mobile, user.MobileTelephoneNumber),
|
|
||||||
Address: util.GetMaxLenStr(user.RegisteredAddress, user.PostalAddress),
|
|
||||||
})
|
|
||||||
uuids = append(uuids, user.Uuid)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
existUuids := object.GetExistUuids(ldapServer.Owner, uuids)
|
existUuids := object.GetExistUuids(ldapServer.Owner, uuids)
|
||||||
|
|
||||||
c.ResponseOk(resp, existUuids)
|
resp := LdapResp{
|
||||||
|
Users: object.AutoAdjustLdapUser(users),
|
||||||
|
ExistUuids: existUuids,
|
||||||
|
}
|
||||||
|
c.ResponseOk(resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLdaps
|
// GetLdaps
|
||||||
@ -206,7 +197,7 @@ func (c *ApiController) DeleteLdap() {
|
|||||||
func (c *ApiController) SyncLdapUsers() {
|
func (c *ApiController) SyncLdapUsers() {
|
||||||
owner := c.Input().Get("owner")
|
owner := c.Input().Get("owner")
|
||||||
ldapId := c.Input().Get("ldapId")
|
ldapId := c.Input().Get("ldapId")
|
||||||
var users []object.LdapRespUser
|
var users []object.LdapUser
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &users)
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &users)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
@ -215,10 +206,10 @@ func (c *ApiController) SyncLdapUsers() {
|
|||||||
|
|
||||||
object.UpdateLdapSyncTime(ldapId)
|
object.UpdateLdapSyncTime(ldapId)
|
||||||
|
|
||||||
exist, failed := object.SyncLdapUsers(owner, users, ldapId)
|
exist, failed, _ := object.SyncLdapUsers(owner, users, ldapId)
|
||||||
|
|
||||||
c.ResponseOk(&LdapSyncResp{
|
c.ResponseOk(&LdapSyncResp{
|
||||||
Exist: *exist,
|
Exist: exist,
|
||||||
Failed: *failed,
|
Failed: failed,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -148,3 +148,16 @@ func (c *ApiController) GetDefaultApplication() {
|
|||||||
maskedApplication := object.GetMaskedApplication(application, userId)
|
maskedApplication := object.GetMaskedApplication(application, userId)
|
||||||
c.ResponseOk(maskedApplication)
|
c.ResponseOk(maskedApplication)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetOrganizationNames ...
|
||||||
|
// @Title GetOrganizationNames
|
||||||
|
// @Tag Organization API
|
||||||
|
// @Param owner query string true "owner"
|
||||||
|
// @Description get all organization names
|
||||||
|
// @Success 200 {array} object.Organization The Response object
|
||||||
|
// @router /get-organization-names [get]
|
||||||
|
func (c *ApiController) GetOrganizationNames() {
|
||||||
|
owner := c.Input().Get("owner")
|
||||||
|
organizationNames := object.GetOrganizationsByFields(owner, "name")
|
||||||
|
c.ResponseOk(organizationNames)
|
||||||
|
}
|
||||||
|
@ -16,7 +16,6 @@ package controllers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/beego/beego/utils/pagination"
|
"github.com/beego/beego/utils/pagination"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
@ -156,15 +155,15 @@ func (c *ApiController) NotifyPayment() {
|
|||||||
|
|
||||||
body := c.Ctx.Input.RequestBody
|
body := c.Ctx.Input.RequestBody
|
||||||
|
|
||||||
ok := object.NotifyPayment(c.Ctx.Request, body, owner, providerName, productName, paymentName)
|
err, errorResponse := object.NotifyPayment(c.Ctx.Request, body, owner, providerName, productName, paymentName)
|
||||||
if ok {
|
|
||||||
_, err := c.Ctx.ResponseWriter.Write([]byte("success"))
|
_, err2 := c.Ctx.ResponseWriter.Write([]byte(errorResponse))
|
||||||
if err != nil {
|
if err2 != nil {
|
||||||
c.ResponseError(err.Error())
|
panic(err2)
|
||||||
return
|
}
|
||||||
}
|
|
||||||
} else {
|
if err != nil {
|
||||||
panic(fmt.Errorf("NotifyPayment() failed: %v", ok))
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
50
controllers/permission_upload.go
Normal file
50
controllers/permission_upload.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
// 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 controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *ApiController) UploadPermissions() {
|
||||||
|
userId := c.GetSessionUsername()
|
||||||
|
owner, user := util.GetOwnerAndNameFromId(userId)
|
||||||
|
|
||||||
|
file, header, err := c.Ctx.Request.FormFile("file")
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fileId := fmt.Sprintf("%s_%s_%s", owner, user, util.RemoveExt(header.Filename))
|
||||||
|
|
||||||
|
path := util.GetUploadXlsxPath(fileId)
|
||||||
|
util.EnsureFileFolderExists(path)
|
||||||
|
err = saveFile(path, &file)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
affected := object.UploadPermissions(owner, fileId)
|
||||||
|
if affected {
|
||||||
|
c.ResponseOk()
|
||||||
|
} else {
|
||||||
|
c.ResponseError(c.T("user_upload:Failed to import users"))
|
||||||
|
}
|
||||||
|
}
|
137
controllers/plan.go
Normal file
137
controllers/plan.go
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
// 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 controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/beego/beego/utils/pagination"
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetPlans
|
||||||
|
// @Title GetPlans
|
||||||
|
// @Tag Plan API
|
||||||
|
// @Description get plans
|
||||||
|
// @Param owner query string true "The owner of plans"
|
||||||
|
// @Success 200 {array} object.Plan The Response object
|
||||||
|
// @router /get-plans [get]
|
||||||
|
func (c *ApiController) GetPlans() {
|
||||||
|
owner := c.Input().Get("owner")
|
||||||
|
limit := c.Input().Get("pageSize")
|
||||||
|
page := c.Input().Get("p")
|
||||||
|
field := c.Input().Get("field")
|
||||||
|
value := c.Input().Get("value")
|
||||||
|
sortField := c.Input().Get("sortField")
|
||||||
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
if limit == "" || page == "" {
|
||||||
|
c.Data["json"] = object.GetPlans(owner)
|
||||||
|
c.ServeJSON()
|
||||||
|
} else {
|
||||||
|
limit := util.ParseInt(limit)
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetPlanCount(owner, field, value)))
|
||||||
|
plan := object.GetPaginatedPlans(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||||
|
c.ResponseOk(plan, paginator.Nums())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPlan
|
||||||
|
// @Title GetPlan
|
||||||
|
// @Tag Plan API
|
||||||
|
// @Description get plan
|
||||||
|
// @Param id query string true "The id ( owner/name ) of the plan"
|
||||||
|
// @Param includeOption query bool false "Should include plan's option"
|
||||||
|
// @Success 200 {object} object.Plan The Response object
|
||||||
|
// @router /get-plan [get]
|
||||||
|
func (c *ApiController) GetPlan() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
includeOption := c.Input().Get("includeOption") == "true"
|
||||||
|
|
||||||
|
plan := object.GetPlan(id)
|
||||||
|
|
||||||
|
if includeOption {
|
||||||
|
options := object.GetPermissionsByRole(plan.Role)
|
||||||
|
|
||||||
|
for _, option := range options {
|
||||||
|
plan.Options = append(plan.Options, option.DisplayName)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = plan
|
||||||
|
} else {
|
||||||
|
c.Data["json"] = plan
|
||||||
|
}
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePlan
|
||||||
|
// @Title UpdatePlan
|
||||||
|
// @Tag Plan API
|
||||||
|
// @Description update plan
|
||||||
|
// @Param id query string true "The id ( owner/name ) of the plan"
|
||||||
|
// @Param body body object.Plan true "The details of the plan"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /update-plan [post]
|
||||||
|
func (c *ApiController) UpdatePlan() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
var plan object.Plan
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &plan)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.UpdatePlan(id, &plan))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddPlan
|
||||||
|
// @Title AddPlan
|
||||||
|
// @Tag Plan API
|
||||||
|
// @Description add plan
|
||||||
|
// @Param body body object.Plan true "The details of the plan"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /add-plan [post]
|
||||||
|
func (c *ApiController) AddPlan() {
|
||||||
|
var plan object.Plan
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &plan)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.AddPlan(&plan))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeletePlan
|
||||||
|
// @Title DeletePlan
|
||||||
|
// @Tag Plan API
|
||||||
|
// @Description delete plan
|
||||||
|
// @Param body body object.Plan true "The details of the plan"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /delete-plan [post]
|
||||||
|
func (c *ApiController) DeletePlan() {
|
||||||
|
var plan object.Plan
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &plan)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.DeletePlan(&plan))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
125
controllers/pricing.go
Normal file
125
controllers/pricing.go
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
// 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 controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/beego/beego/utils/pagination"
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetPricings
|
||||||
|
// @Title GetPricings
|
||||||
|
// @Tag Pricing API
|
||||||
|
// @Description get pricings
|
||||||
|
// @Param owner query string true "The owner of pricings"
|
||||||
|
// @Success 200 {array} object.Pricing The Response object
|
||||||
|
// @router /get-pricings [get]
|
||||||
|
func (c *ApiController) GetPricings() {
|
||||||
|
owner := c.Input().Get("owner")
|
||||||
|
limit := c.Input().Get("pageSize")
|
||||||
|
page := c.Input().Get("p")
|
||||||
|
field := c.Input().Get("field")
|
||||||
|
value := c.Input().Get("value")
|
||||||
|
sortField := c.Input().Get("sortField")
|
||||||
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
if limit == "" || page == "" {
|
||||||
|
c.Data["json"] = object.GetPricings(owner)
|
||||||
|
c.ServeJSON()
|
||||||
|
} else {
|
||||||
|
limit := util.ParseInt(limit)
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetPricingCount(owner, field, value)))
|
||||||
|
pricing := object.GetPaginatedPricings(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||||
|
c.ResponseOk(pricing, paginator.Nums())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPricing
|
||||||
|
// @Title GetPricing
|
||||||
|
// @Tag Pricing API
|
||||||
|
// @Description get pricing
|
||||||
|
// @Param id query string true "The id ( owner/name ) of the pricing"
|
||||||
|
// @Success 200 {object} object.pricing The Response object
|
||||||
|
// @router /get-pricing [get]
|
||||||
|
func (c *ApiController) GetPricing() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
pricing := object.GetPricing(id)
|
||||||
|
|
||||||
|
c.Data["json"] = pricing
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePricing
|
||||||
|
// @Title UpdatePricing
|
||||||
|
// @Tag Pricing API
|
||||||
|
// @Description update pricing
|
||||||
|
// @Param id query string true "The id ( owner/name ) of the pricing"
|
||||||
|
// @Param body body object.Pricing true "The details of the pricing"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /update-pricing [post]
|
||||||
|
func (c *ApiController) UpdatePricing() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
var pricing object.Pricing
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &pricing)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.UpdatePricing(id, &pricing))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddPricing
|
||||||
|
// @Title AddPricing
|
||||||
|
// @Tag Pricing API
|
||||||
|
// @Description add pricing
|
||||||
|
// @Param body body object.Pricing true "The details of the pricing"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /add-pricing [post]
|
||||||
|
func (c *ApiController) AddPricing() {
|
||||||
|
var pricing object.Pricing
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &pricing)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.AddPricing(&pricing))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeletePricing
|
||||||
|
// @Title DeletePricing
|
||||||
|
// @Tag Pricing API
|
||||||
|
// @Description delete pricing
|
||||||
|
// @Param body body object.Pricing true "The details of the pricing"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /delete-pricing [post]
|
||||||
|
func (c *ApiController) DeletePricing() {
|
||||||
|
var pricing object.Pricing
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &pricing)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.DeletePricing(&pricing))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
@ -37,13 +37,19 @@ func (c *ApiController) GetProviders() {
|
|||||||
value := c.Input().Get("value")
|
value := c.Input().Get("value")
|
||||||
sortField := c.Input().Get("sortField")
|
sortField := c.Input().Get("sortField")
|
||||||
sortOrder := c.Input().Get("sortOrder")
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
|
||||||
|
ok, isMaskEnabled := c.IsMaskedEnabled()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if limit == "" || page == "" {
|
if limit == "" || page == "" {
|
||||||
c.Data["json"] = object.GetMaskedProviders(object.GetProviders(owner))
|
c.Data["json"] = object.GetMaskedProviders(object.GetProviders(owner), isMaskEnabled)
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
} else {
|
} else {
|
||||||
limit := util.ParseInt(limit)
|
limit := util.ParseInt(limit)
|
||||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetProviderCount(owner, field, value)))
|
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetProviderCount(owner, field, value)))
|
||||||
providers := object.GetMaskedProviders(object.GetPaginationProviders(owner, paginator.Offset(), limit, field, value, sortField, sortOrder))
|
providers := object.GetMaskedProviders(object.GetPaginationProviders(owner, paginator.Offset(), limit, field, value, sortField, sortOrder), isMaskEnabled)
|
||||||
c.ResponseOk(providers, paginator.Nums())
|
c.ResponseOk(providers, paginator.Nums())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -61,13 +67,19 @@ func (c *ApiController) GetGlobalProviders() {
|
|||||||
value := c.Input().Get("value")
|
value := c.Input().Get("value")
|
||||||
sortField := c.Input().Get("sortField")
|
sortField := c.Input().Get("sortField")
|
||||||
sortOrder := c.Input().Get("sortOrder")
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
|
||||||
|
ok, isMaskEnabled := c.IsMaskedEnabled()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if limit == "" || page == "" {
|
if limit == "" || page == "" {
|
||||||
c.Data["json"] = object.GetMaskedProviders(object.GetGlobalProviders())
|
c.Data["json"] = object.GetMaskedProviders(object.GetGlobalProviders(), isMaskEnabled)
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
} else {
|
} else {
|
||||||
limit := util.ParseInt(limit)
|
limit := util.ParseInt(limit)
|
||||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetGlobalProviderCount(field, value)))
|
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetGlobalProviderCount(field, value)))
|
||||||
providers := object.GetMaskedProviders(object.GetPaginationGlobalProviders(paginator.Offset(), limit, field, value, sortField, sortOrder))
|
providers := object.GetMaskedProviders(object.GetPaginationGlobalProviders(paginator.Offset(), limit, field, value, sortField, sortOrder), isMaskEnabled)
|
||||||
c.ResponseOk(providers, paginator.Nums())
|
c.ResponseOk(providers, paginator.Nums())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -81,7 +93,13 @@ func (c *ApiController) GetGlobalProviders() {
|
|||||||
// @router /get-provider [get]
|
// @router /get-provider [get]
|
||||||
func (c *ApiController) GetProvider() {
|
func (c *ApiController) GetProvider() {
|
||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
c.Data["json"] = object.GetMaskedProvider(object.GetProvider(id))
|
|
||||||
|
ok, isMaskEnabled := c.IsMaskedEnabled()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = object.GetMaskedProvider(object.GetProvider(id), isMaskEnabled)
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
50
controllers/role_upload.go
Normal file
50
controllers/role_upload.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
// 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 controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *ApiController) UploadRoles() {
|
||||||
|
userId := c.GetSessionUsername()
|
||||||
|
owner, user := util.GetOwnerAndNameFromId(userId)
|
||||||
|
|
||||||
|
file, header, err := c.Ctx.Request.FormFile("file")
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fileId := fmt.Sprintf("%s_%s_%s", owner, user, util.RemoveExt(header.Filename))
|
||||||
|
|
||||||
|
path := util.GetUploadXlsxPath(fileId)
|
||||||
|
util.EnsureFileFolderExists(path)
|
||||||
|
err = saveFile(path, &file)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
affected := object.UploadRoles(owner, fileId)
|
||||||
|
if affected {
|
||||||
|
c.ResponseOk()
|
||||||
|
} else {
|
||||||
|
c.ResponseError(c.T("user_upload:Failed to import users"))
|
||||||
|
}
|
||||||
|
}
|
125
controllers/subscription.go
Normal file
125
controllers/subscription.go
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
// 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 controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/beego/beego/utils/pagination"
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetSubscriptions
|
||||||
|
// @Title GetSubscriptions
|
||||||
|
// @Tag Subscription API
|
||||||
|
// @Description get subscriptions
|
||||||
|
// @Param owner query string true "The owner of subscriptions"
|
||||||
|
// @Success 200 {array} object.Subscription The Response object
|
||||||
|
// @router /get-subscriptions [get]
|
||||||
|
func (c *ApiController) GetSubscriptions() {
|
||||||
|
owner := c.Input().Get("owner")
|
||||||
|
limit := c.Input().Get("pageSize")
|
||||||
|
page := c.Input().Get("p")
|
||||||
|
field := c.Input().Get("field")
|
||||||
|
value := c.Input().Get("value")
|
||||||
|
sortField := c.Input().Get("sortField")
|
||||||
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
if limit == "" || page == "" {
|
||||||
|
c.Data["json"] = object.GetSubscriptions(owner)
|
||||||
|
c.ServeJSON()
|
||||||
|
} else {
|
||||||
|
limit := util.ParseInt(limit)
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetSubscriptionCount(owner, field, value)))
|
||||||
|
subscription := object.GetPaginationSubscriptions(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||||
|
c.ResponseOk(subscription, paginator.Nums())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSubscription
|
||||||
|
// @Title GetSubscription
|
||||||
|
// @Tag Subscription API
|
||||||
|
// @Description get subscription
|
||||||
|
// @Param id query string true "The id ( owner/name ) of the subscription"
|
||||||
|
// @Success 200 {object} object.subscription The Response object
|
||||||
|
// @router /get-subscription [get]
|
||||||
|
func (c *ApiController) GetSubscription() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
subscription := object.GetSubscription(id)
|
||||||
|
|
||||||
|
c.Data["json"] = subscription
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSubscription
|
||||||
|
// @Title UpdateSubscription
|
||||||
|
// @Tag Subscription API
|
||||||
|
// @Description update subscription
|
||||||
|
// @Param id query string true "The id ( owner/name ) of the subscription"
|
||||||
|
// @Param body body object.Subscription true "The details of the subscription"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /update-subscription [post]
|
||||||
|
func (c *ApiController) UpdateSubscription() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
var subscription object.Subscription
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &subscription)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.UpdateSubscription(id, &subscription))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddSubscription
|
||||||
|
// @Title AddSubscription
|
||||||
|
// @Tag Subscription API
|
||||||
|
// @Description add subscription
|
||||||
|
// @Param body body object.Subscription true "The details of the subscription"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /add-subscription [post]
|
||||||
|
func (c *ApiController) AddSubscription() {
|
||||||
|
var subscription object.Subscription
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &subscription)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.AddSubscription(&subscription))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSubscription
|
||||||
|
// @Title DeleteSubscription
|
||||||
|
// @Tag Subscription API
|
||||||
|
// @Description delete subscription
|
||||||
|
// @Param body body object.Subscription true "The details of the subscription"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /delete-subscription [post]
|
||||||
|
func (c *ApiController) DeleteSubscription() {
|
||||||
|
var subscription object.Subscription
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &subscription)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.DeleteSubscription(&subscription))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
@ -59,3 +59,13 @@ func (c *ApiController) GetVersionInfo() {
|
|||||||
|
|
||||||
c.ResponseOk(versionInfo)
|
c.ResponseOk(versionInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Health
|
||||||
|
// @Title Health
|
||||||
|
// @Tag System API
|
||||||
|
// @Description check if the system is live
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /health [get]
|
||||||
|
func (c *ApiController) Health() {
|
||||||
|
c.ResponseOk()
|
||||||
|
}
|
||||||
|
@ -80,7 +80,7 @@ func (c *ApiController) GetUsers() {
|
|||||||
// @Title GetUser
|
// @Title GetUser
|
||||||
// @Tag User API
|
// @Tag User API
|
||||||
// @Description get user
|
// @Description get user
|
||||||
// @Param id query string true "The id ( owner/name ) of the user"
|
// @Param id query string false "The id ( owner/name ) of the user"
|
||||||
// @Param owner query string false "The owner of the user"
|
// @Param owner query string false "The owner of the user"
|
||||||
// @Param email query string false "The email of the user"
|
// @Param email query string false "The email of the user"
|
||||||
// @Param phone query string false "The phone of the user"
|
// @Param phone query string false "The phone of the user"
|
||||||
@ -92,13 +92,19 @@ func (c *ApiController) GetUser() {
|
|||||||
email := c.Input().Get("email")
|
email := c.Input().Get("email")
|
||||||
phone := c.Input().Get("phone")
|
phone := c.Input().Get("phone")
|
||||||
userId := c.Input().Get("userId")
|
userId := c.Input().Get("userId")
|
||||||
|
|
||||||
owner := c.Input().Get("owner")
|
owner := c.Input().Get("owner")
|
||||||
|
|
||||||
|
var userFromUserId *object.User
|
||||||
|
if userId != "" && owner != "" {
|
||||||
|
userFromUserId = object.GetUserByUserId(owner, userId)
|
||||||
|
id = util.GetId(userFromUserId.Owner, userFromUserId.Name)
|
||||||
|
}
|
||||||
|
|
||||||
if owner == "" {
|
if owner == "" {
|
||||||
owner = util.GetOwnerFromId(id)
|
owner = util.GetOwnerFromId(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", owner))
|
organization := object.GetOrganization(util.GetId("admin", owner))
|
||||||
if !organization.IsProfilePublic {
|
if !organization.IsProfilePublic {
|
||||||
requestUserId := c.GetSessionUsername()
|
requestUserId := c.GetSessionUsername()
|
||||||
hasPermission, err := object.CheckUserPermission(requestUserId, id, false, c.GetAcceptLanguage())
|
hasPermission, err := object.CheckUserPermission(requestUserId, id, false, c.GetAcceptLanguage())
|
||||||
@ -115,7 +121,7 @@ func (c *ApiController) GetUser() {
|
|||||||
case phone != "":
|
case phone != "":
|
||||||
user = object.GetUserByPhone(owner, phone)
|
user = object.GetUserByPhone(owner, phone)
|
||||||
case userId != "":
|
case userId != "":
|
||||||
user = object.GetUserByUserId(owner, userId)
|
user = userFromUserId
|
||||||
default:
|
default:
|
||||||
user = object.GetUser(id)
|
user = object.GetUser(id)
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,6 @@ package controllers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/conf"
|
"github.com/casdoor/casdoor/conf"
|
||||||
"github.com/casdoor/casdoor/i18n"
|
"github.com/casdoor/casdoor/i18n"
|
||||||
@ -115,12 +114,25 @@ func (c *ApiController) RequireAdmin() (string, bool) {
|
|||||||
return user.Owner, true
|
return user.Owner, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func getInitScore(organization *object.Organization) (int, error) {
|
// IsMaskedEnabled ...
|
||||||
if organization != nil {
|
func (c *ApiController) IsMaskedEnabled() (bool, bool) {
|
||||||
return organization.InitScore, nil
|
isMaskEnabled := true
|
||||||
} else {
|
withSecret := c.Input().Get("withSecret")
|
||||||
return strconv.Atoi(conf.GetConfigString("initScore"))
|
if withSecret == "1" {
|
||||||
|
isMaskEnabled = false
|
||||||
|
|
||||||
|
if conf.IsDemoMode() {
|
||||||
|
c.ResponseError(c.T("general:this operation is not allowed in demo mode"))
|
||||||
|
return false, isMaskEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok := c.RequireAdmin()
|
||||||
|
if !ok {
|
||||||
|
return false, isMaskEnabled
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true, isMaskEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ApiController) GetProviderFromContext(category string) (*object.Provider, *object.User, bool) {
|
func (c *ApiController) GetProviderFromContext(category string) (*object.Provider, *object.User, bool) {
|
||||||
|
@ -54,4 +54,7 @@ type AuthForm struct {
|
|||||||
MfaType string `json:"mfaType"`
|
MfaType string `json:"mfaType"`
|
||||||
Passcode string `json:"passcode"`
|
Passcode string `json:"passcode"`
|
||||||
RecoveryCode string `json:"recoveryCode"`
|
RecoveryCode string `json:"recoveryCode"`
|
||||||
|
|
||||||
|
Plan string `json:"plan"`
|
||||||
|
Pricing string `json:"pricing"`
|
||||||
}
|
}
|
||||||
|
20
go.mod
20
go.mod
@ -8,8 +8,9 @@ require (
|
|||||||
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387
|
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387
|
||||||
github.com/aliyun/alibaba-cloud-sdk-go v1.62.188 // indirect
|
github.com/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.44.4
|
||||||
github.com/beego/beego v1.12.11
|
github.com/beego/beego v1.12.12
|
||||||
github.com/beevik/etree v1.1.0
|
github.com/beevik/etree v1.1.0
|
||||||
|
github.com/casbin/casbin v1.9.1 // indirect
|
||||||
github.com/casbin/casbin/v2 v2.30.1
|
github.com/casbin/casbin/v2 v2.30.1
|
||||||
github.com/casdoor/go-sms-sender v0.6.1
|
github.com/casdoor/go-sms-sender v0.6.1
|
||||||
github.com/casdoor/gomail/v2 v2.0.1
|
github.com/casdoor/gomail/v2 v2.0.1
|
||||||
@ -18,6 +19,7 @@ require (
|
|||||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||||
github.com/denisenkom/go-mssqldb v0.9.0
|
github.com/denisenkom/go-mssqldb v0.9.0
|
||||||
github.com/dlclark/regexp2 v1.9.0 // indirect
|
github.com/dlclark/regexp2 v1.9.0 // indirect
|
||||||
|
github.com/elazarl/go-bindata-assetfs v1.0.1 // indirect
|
||||||
github.com/fogleman/gg v1.3.0
|
github.com/fogleman/gg v1.3.0
|
||||||
github.com/forestmgy/ldapserver v1.1.0
|
github.com/forestmgy/ldapserver v1.1.0
|
||||||
github.com/go-git/go-git/v5 v5.6.0
|
github.com/go-git/go-git/v5 v5.6.0
|
||||||
@ -28,24 +30,27 @@ require (
|
|||||||
github.com/go-webauthn/webauthn v0.6.0
|
github.com/go-webauthn/webauthn v0.6.0
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||||
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
|
github.com/gorilla/mux v1.7.3 // indirect
|
||||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
||||||
github.com/lestrrat-go/jwx v1.2.21
|
github.com/lestrrat-go/jwx v1.2.21
|
||||||
github.com/lib/pq v1.8.0
|
github.com/lib/pq v1.10.2
|
||||||
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3
|
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3
|
||||||
github.com/markbates/goth v1.75.2
|
github.com/markbates/goth v1.75.2
|
||||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
|
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
|
||||||
github.com/nyaruka/phonenumbers v1.1.5
|
github.com/nyaruka/phonenumbers v1.1.5
|
||||||
github.com/pkoukk/tiktoken-go v0.1.1
|
github.com/pkoukk/tiktoken-go v0.1.1
|
||||||
github.com/prometheus/client_golang v1.7.0
|
github.com/prometheus/client_golang v1.11.1
|
||||||
github.com/prometheus/client_model v0.2.0
|
github.com/prometheus/client_model v0.2.0
|
||||||
github.com/qiangmzsx/string-adapter/v2 v2.1.0
|
github.com/qiangmzsx/string-adapter/v2 v2.1.0
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/russellhaering/gosaml2 v0.6.0
|
github.com/russellhaering/gosaml2 v0.9.0
|
||||||
github.com/russellhaering/goxmldsig v1.1.1
|
github.com/russellhaering/goxmldsig v1.2.0
|
||||||
github.com/sashabaranov/go-openai v1.9.1
|
github.com/sashabaranov/go-openai v1.9.1
|
||||||
github.com/satori/go.uuid v1.2.0
|
github.com/satori/go.uuid v1.2.0
|
||||||
|
github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect
|
||||||
github.com/shirou/gopsutil v3.21.11+incompatible
|
github.com/shirou/gopsutil v3.21.11+incompatible
|
||||||
github.com/siddontang/go-log v0.0.0-20190221022429-1e957dd83bed
|
github.com/siddontang/go-log v0.0.0-20190221022429-1e957dd83bed
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||||
@ -57,10 +62,11 @@ require (
|
|||||||
github.com/xorm-io/xorm v1.1.6
|
github.com/xorm-io/xorm v1.1.6
|
||||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||||
golang.org/x/crypto v0.6.0
|
golang.org/x/crypto v0.6.0
|
||||||
golang.org/x/net v0.6.0
|
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect
|
||||||
|
golang.org/x/net v0.7.0
|
||||||
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914
|
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
gopkg.in/square/go-jose.v2 v2.6.0
|
gopkg.in/square/go-jose.v2 v2.6.0
|
||||||
gopkg.in/yaml.v2 v2.3.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
modernc.org/sqlite v1.10.1-0.20210314190707-798bbeb9bb84
|
modernc.org/sqlite v1.10.1-0.20210314190707-798bbeb9bb84
|
||||||
)
|
)
|
||||||
|
72
go.sum
72
go.sum
@ -73,6 +73,7 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
|
|||||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
|
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||||
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387 h1:loy0fjI90vF44BPW4ZYOkE3tDkGTy7yHURusOJimt+I=
|
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387 h1:loy0fjI90vF44BPW4ZYOkE3tDkGTy7yHURusOJimt+I=
|
||||||
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387/go.mod h1:GuR5j/NW7AU7tDAQUDGCtpiPxWIOy/c3kiRDnlwiCHc=
|
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387/go.mod h1:GuR5j/NW7AU7tDAQUDGCtpiPxWIOy/c3kiRDnlwiCHc=
|
||||||
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||||
@ -92,8 +93,8 @@ github.com/aws/aws-sdk-go v1.44.4 h1:ePN0CVJMdiz2vYUcJH96eyxRrtKGSDMgyhP6rah2OgE
|
|||||||
github.com/aws/aws-sdk-go v1.44.4/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
github.com/aws/aws-sdk-go v1.44.4/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
||||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f h1:ZNv7On9kyUzm7fvRZumSyy/IUiSC7AzL0I1jKKtwooA=
|
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f h1:ZNv7On9kyUzm7fvRZumSyy/IUiSC7AzL0I1jKKtwooA=
|
||||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
|
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
|
||||||
github.com/beego/beego v1.12.11 h1:MWKcnpavb7iAIS0m6uuEq6pHKkYvGNw/5umIUKqL7jM=
|
github.com/beego/beego v1.12.12 h1:ARY1sNVSS23N0mEQIhSqRDTyyDlx95JY0V3GogBbZbQ=
|
||||||
github.com/beego/beego v1.12.11/go.mod h1:QURFL1HldOcCZAxnc1cZ7wrplsYR5dKPHFjmk6WkLAs=
|
github.com/beego/beego v1.12.12/go.mod h1:QURFL1HldOcCZAxnc1cZ7wrplsYR5dKPHFjmk6WkLAs=
|
||||||
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ=
|
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ=
|
||||||
github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU=
|
github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU=
|
||||||
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
|
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
|
||||||
@ -106,8 +107,9 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
|||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
|
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
|
||||||
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||||
github.com/casbin/casbin v1.7.0 h1:PuzlE8w0JBg/DhIqnkF1Dewf3z+qmUZMVN07PonvVUQ=
|
|
||||||
github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=
|
github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=
|
||||||
|
github.com/casbin/casbin v1.9.1 h1:ucjbS5zTrmSLtH4XogqOG920Poe6QatdXtz1FEbApeM=
|
||||||
|
github.com/casbin/casbin v1.9.1/go.mod h1:z8uPsfBJGUsnkagrt3G8QvjgTKFMBJ32UP8HpZllfog=
|
||||||
github.com/casbin/casbin/v2 v2.1.0/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
github.com/casbin/casbin/v2 v2.1.0/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
||||||
github.com/casbin/casbin/v2 v2.28.3/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
|
github.com/casbin/casbin/v2 v2.28.3/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
|
||||||
github.com/casbin/casbin/v2 v2.30.1 h1:P5HWadDL7olwUXNdcuKUBk+x75Y2eitFxYTcLNKeKF0=
|
github.com/casbin/casbin/v2 v2.30.1 h1:P5HWadDL7olwUXNdcuKUBk+x75Y2eitFxYTcLNKeKF0=
|
||||||
@ -168,8 +170,9 @@ github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4
|
|||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||||
github.com/elastic/go-elasticsearch/v6 v6.8.5/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI=
|
github.com/elastic/go-elasticsearch/v6 v6.8.5/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI=
|
||||||
github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk=
|
|
||||||
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
|
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
|
||||||
|
github.com/elazarl/go-bindata-assetfs v1.0.1 h1:m0kkaHRKEu7tUIUFVwhGGGYClXvyl4RE03qmvRTNfbw=
|
||||||
|
github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
|
||||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
@ -205,10 +208,12 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2
|
|||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
|
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||||
github.com/go-ldap/ldap/v3 v3.3.0 h1:lwx+SJpgOHd8tG6SumBQZXCmNX51zM8B1cfxJ5gv4tQ=
|
github.com/go-ldap/ldap/v3 v3.3.0 h1:lwx+SJpgOHd8tG6SumBQZXCmNX51zM8B1cfxJ5gv4tQ=
|
||||||
github.com/go-ldap/ldap/v3 v3.3.0/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg=
|
github.com/go-ldap/ldap/v3 v3.3.0/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg=
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
|
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||||
github.com/go-mysql-org/go-mysql v1.7.0 h1:qE5FTRb3ZeTQmlk3pjE+/m2ravGxxRDrVDTyDe9tvqI=
|
github.com/go-mysql-org/go-mysql v1.7.0 h1:qE5FTRb3ZeTQmlk3pjE+/m2ravGxxRDrVDTyDe9tvqI=
|
||||||
github.com/go-mysql-org/go-mysql v1.7.0/go.mod h1:9cRWLtuXNKhamUPMkrDVzBhaomGvqLRLtBiyjvjc4pk=
|
github.com/go-mysql-org/go-mysql v1.7.0/go.mod h1:9cRWLtuXNKhamUPMkrDVzBhaomGvqLRLtBiyjvjc4pk=
|
||||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||||
@ -273,8 +278,10 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
|
|||||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
|
|
||||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||||
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||||
@ -292,6 +299,8 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/go-tpm v0.1.2-0.20190725015402-ae6dd98980d4/go.mod h1:H9HbmUG2YgV/PHITkO7p6wxEEj/v5nlsVWIwumwH2NI=
|
github.com/google/go-tpm v0.1.2-0.20190725015402-ae6dd98980d4/go.mod h1:H9HbmUG2YgV/PHITkO7p6wxEEj/v5nlsVWIwumwH2NI=
|
||||||
@ -321,8 +330,9 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m
|
|||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||||
github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=
|
|
||||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
|
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
|
||||||
|
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1 h1:LqbZZ9sNMWVjeXS4NN5oVvhMjDyLhmA1LG86oSo+IqY=
|
github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1 h1:LqbZZ9sNMWVjeXS4NN5oVvhMjDyLhmA1LG86oSo+IqY=
|
||||||
github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1/go.mod h1:YeAe0gNeiNT5hoiZRI4yiOky6jVdNvfO2N6Kav/HmxY=
|
github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1/go.mod h1:YeAe0gNeiNT5hoiZRI4yiOky6jVdNvfO2N6Kav/HmxY=
|
||||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||||
@ -356,9 +366,9 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw
|
|||||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||||
github.com/jmoiron/sqlx v1.3.3/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ=
|
github.com/jmoiron/sqlx v1.3.3/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ=
|
||||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||||
github.com/jonboulle/clockwork v0.2.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
|
|
||||||
github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ=
|
github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ=
|
||||||
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
|
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
|
||||||
|
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.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
@ -368,6 +378,7 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
|
|||||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
|
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
|
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
|
||||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||||
@ -377,6 +388,7 @@ github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF
|
|||||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
@ -408,8 +420,9 @@ github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmt
|
|||||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg=
|
|
||||||
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
|
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
|
||||||
|
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3 h1:wIONC+HMNRqmWBjuMxhatuSzHaljStc4gjDeKycxy0A=
|
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3 h1:wIONC+HMNRqmWBjuMxhatuSzHaljStc4gjDeKycxy0A=
|
||||||
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3/go.mod h1:37YR9jabpiIxsb8X9VCIx8qFOjTDIIrIHHODa8C4gz0=
|
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3/go.mod h1:37YR9jabpiIxsb8X9VCIx8qFOjTDIIrIHHODa8C4gz0=
|
||||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
@ -419,8 +432,8 @@ github.com/markbates/goth v1.75.2 h1:C7KloBMMk50JyXaHhzfqWYLW6+bDcSVIvUGHXneLWro
|
|||||||
github.com/markbates/goth v1.75.2/go.mod h1:X6xdNgpapSENS0O35iTBBcMHoJDQDfI9bJl+APCkYMc=
|
github.com/markbates/goth v1.75.2/go.mod h1:X6xdNgpapSENS0O35iTBBcMHoJDQDfI9bJl+APCkYMc=
|
||||||
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
|
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
|
||||||
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
|
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
|
||||||
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911 h1:erppMjjp69Rertg1zlgRbLJH1u+eCmRPxKjMZ5I8/Ro=
|
github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU=
|
||||||
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To=
|
github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To=
|
||||||
github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI=
|
github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI=
|
||||||
github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
|
github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
|
||||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||||
@ -445,6 +458,7 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
|
|||||||
github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c h1:3wkDRdxK92dF+c1ke2dtj7ZzemFWBHB9plnJOtlwdFA=
|
github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c h1:3wkDRdxK92dF+c1ke2dtj7ZzemFWBHB9plnJOtlwdFA=
|
||||||
github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM=
|
github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM=
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
|
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
|
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
|
||||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
|
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
|
||||||
@ -486,8 +500,10 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
|||||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||||
github.com/prometheus/client_golang v1.7.0 h1:wCi7urQOGBsYcQROHqpUUX4ct84xp40t9R9JX0FuA/U=
|
|
||||||
github.com/prometheus/client_golang v1.7.0/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
github.com/prometheus/client_golang v1.7.0/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||||
|
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||||
|
github.com/prometheus/client_golang v1.11.1 h1:+4eQaD7vAZ6DsfsxB15hbE0odUjGI5ARs9yskGu1v4s=
|
||||||
|
github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
@ -496,13 +512,15 @@ github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6T
|
|||||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc=
|
|
||||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||||
|
github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ=
|
||||||
|
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
||||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8=
|
|
||||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||||
|
github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
|
||||||
|
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||||
github.com/qiangmzsx/string-adapter/v2 v2.1.0 h1:q0y8TPa/sTwtriJPRe8gWL++PuZ+XbOUuvKU+hvtTYs=
|
github.com/qiangmzsx/string-adapter/v2 v2.1.0 h1:q0y8TPa/sTwtriJPRe8gWL++PuZ+XbOUuvKU+hvtTYs=
|
||||||
github.com/qiangmzsx/string-adapter/v2 v2.1.0/go.mod h1:PElPB7b7HnGKTsuADAffFpOQXHqjEGJz1+U1a6yR5wA=
|
github.com/qiangmzsx/string-adapter/v2 v2.1.0/go.mod h1:PElPB7b7HnGKTsuADAffFpOQXHqjEGJz1+U1a6yR5wA=
|
||||||
@ -518,11 +536,10 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
|
|||||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||||
github.com/russellhaering/gosaml2 v0.6.0 h1:OED8FLgczXxXAPlKhnJHQfmEig52tDX2qeXdPtZRIKc=
|
github.com/russellhaering/gosaml2 v0.9.0 h1:CNMnH42z/GirrKjdmNrSS6bAAs47F9bPdl4PfRmVOIk=
|
||||||
github.com/russellhaering/gosaml2 v0.6.0/go.mod h1:CtzxpPr4+bevsATaqR0rw3aqrNlX274b+3C6vFTLCk8=
|
github.com/russellhaering/gosaml2 v0.9.0/go.mod h1:byViER/1YPUa0Puj9ROZblpoq2jsE7h/CJmitzX0geU=
|
||||||
github.com/russellhaering/goxmldsig v1.1.0/go.mod h1:QK8GhXPB3+AfuCrfo0oRISa9NfzeCpWmxeGnqEpDF9o=
|
github.com/russellhaering/goxmldsig v1.2.0 h1:Y6GTTc9Un5hCxSzVz4UIWQ/zuVwDvzJk80guqzwx6Vg=
|
||||||
github.com/russellhaering/goxmldsig v1.1.1 h1:vI0r2osGF1A9PLvsGdPUAGwEIrKa4Pj5sesSBsebIxM=
|
github.com/russellhaering/goxmldsig v1.2.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw=
|
||||||
github.com/russellhaering/goxmldsig v1.1.1/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw=
|
|
||||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/sashabaranov/go-openai v1.9.1 h1:3N52HkJKo9Zlo/oe1AVv5ZkCOny0ra58/ACvAxkN3MM=
|
github.com/sashabaranov/go-openai v1.9.1 h1:3N52HkJKo9Zlo/oe1AVv5ZkCOny0ra58/ACvAxkN3MM=
|
||||||
@ -531,8 +548,9 @@ github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
|||||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||||
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 h1:X+yvsM2yrEktyI+b2qND5gpH8YhURn0k8OCaeRnkINo=
|
|
||||||
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg=
|
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg=
|
||||||
|
github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 h1:DAYUYH5869yV94zvCES9F51oYtN5oGlwjxJJz7ZCnik=
|
||||||
|
github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg=
|
||||||
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
|
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
|
||||||
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 h1:pntxY8Ary0t43dCZ5dqY4YTJCObLY1kIXl0uzMv+7DE=
|
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 h1:pntxY8Ary0t43dCZ5dqY4YTJCObLY1kIXl0uzMv+7DE=
|
||||||
@ -548,6 +566,7 @@ github.com/siddontang/goredis v0.0.0-20150324035039-760763f78400/go.mod h1:DDcKz
|
|||||||
github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
|
github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
|
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
github.com/skeema/knownhosts v1.1.0 h1:Wvr9V0MxhjRbl3f9nMnKnFfiWTJmtECJ9Njkea3ysW0=
|
github.com/skeema/knownhosts v1.1.0 h1:Wvr9V0MxhjRbl3f9nMnKnFfiWTJmtECJ9Njkea3ysW0=
|
||||||
github.com/skeema/knownhosts v1.1.0/go.mod h1:sKFq3RD6/TKZkSWn8boUbDC7Qkgcv+8XXijpFO6roag=
|
github.com/skeema/knownhosts v1.1.0/go.mod h1:sKFq3RD6/TKZkSWn8boUbDC7Qkgcv+8XXijpFO6roag=
|
||||||
@ -700,8 +719,9 @@ golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHl
|
|||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
|
|
||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
|
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
|
||||||
|
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||||
@ -758,8 +778,9 @@ golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfS
|
|||||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||||
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||||
golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=
|
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
|
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||||
|
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
@ -817,6 +838,7 @@ golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@ -827,6 +849,7 @@ golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210629170331-7dc0b73dc9fb/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210629170331-7dc0b73dc9fb/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
@ -1005,8 +1028,10 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
|
|||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||||
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
|
||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
|
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
||||||
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||||
@ -1039,8 +1064,9 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
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-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-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.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
@ -32,6 +32,7 @@ func TestGenerateI18nFrontend(t *testing.T) {
|
|||||||
applyToOtherLanguage("frontend", "ko", data)
|
applyToOtherLanguage("frontend", "ko", data)
|
||||||
applyToOtherLanguage("frontend", "ru", data)
|
applyToOtherLanguage("frontend", "ru", data)
|
||||||
applyToOtherLanguage("frontend", "vi", data)
|
applyToOtherLanguage("frontend", "vi", data)
|
||||||
|
applyToOtherLanguage("frontend", "pt", data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGenerateI18nBackend(t *testing.T) {
|
func TestGenerateI18nBackend(t *testing.T) {
|
||||||
@ -47,4 +48,5 @@ func TestGenerateI18nBackend(t *testing.T) {
|
|||||||
applyToOtherLanguage("backend", "ko", data)
|
applyToOtherLanguage("backend", "ko", data)
|
||||||
applyToOtherLanguage("backend", "ru", data)
|
applyToOtherLanguage("backend", "ru", data)
|
||||||
applyToOtherLanguage("backend", "vi", data)
|
applyToOtherLanguage("backend", "vi", data)
|
||||||
|
applyToOtherLanguage("backend", "pt", data)
|
||||||
}
|
}
|
||||||
|
@ -68,10 +68,10 @@
|
|||||||
"Missing parameter": "Thiếu tham số",
|
"Missing parameter": "Thiếu tham số",
|
||||||
"Please login first": "Vui lòng đăng nhập trước",
|
"Please login first": "Vui lòng đăng nhập trước",
|
||||||
"The user: %s doesn't exist": "Người dùng: %s không tồn tại",
|
"The user: %s doesn't exist": "Người dùng: %s không tồn tại",
|
||||||
"don't support captchaProvider: ": "Không hỗ trợ captchaProvider:"
|
"don't support captchaProvider: ": "không hỗ trợ captchaProvider: "
|
||||||
},
|
},
|
||||||
"ldap": {
|
"ldap": {
|
||||||
"Ldap server exist": "Máy chủ Ldap tồn tại"
|
"Ldap server exist": "Máy chủ LDAP tồn tại"
|
||||||
},
|
},
|
||||||
"link": {
|
"link": {
|
||||||
"Please link first": "Vui lòng kết nối trước tiên",
|
"Please link first": "Vui lòng kết nối trước tiên",
|
||||||
|
@ -83,7 +83,7 @@
|
|||||||
},
|
},
|
||||||
"organization": {
|
"organization": {
|
||||||
"Only admin can modify the %s.": "仅允许管理员可以修改%s",
|
"Only admin can modify the %s.": "仅允许管理员可以修改%s",
|
||||||
"The %s is immutable.": "%s是不可变的",
|
"The %s is immutable.": "%s 是不可变的",
|
||||||
"Unknown modify rule %s.": "未知的修改规则: %s"
|
"Unknown modify rule %s.": "未知的修改规则: %s"
|
||||||
},
|
},
|
||||||
"provider": {
|
"provider": {
|
||||||
@ -143,7 +143,7 @@
|
|||||||
"the user does not exist, please sign up first": "用户不存在,请先注册"
|
"the user does not exist, please sign up first": "用户不存在,请先注册"
|
||||||
},
|
},
|
||||||
"webauthn": {
|
"webauthn": {
|
||||||
"Found no credentials for this user": "该用户没有WebAuthn凭据",
|
"Found no credentials for this user": "该用户没有 WebAuthn 凭据",
|
||||||
"Please call WebAuthnSigninBegin first": "请先调用WebAuthnSigninBegin函数"
|
"Please call WebAuthnSigninBegin first": "请先调用WebAuthnSigninBegin函数"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
22
i18n/util.go
22
i18n/util.go
@ -73,23 +73,27 @@ func applyData(data1 *I18nData, data2 *I18nData) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Translate(lang string, error string) string {
|
func Translate(language string, errorText string) string {
|
||||||
tokens := strings.SplitN(error, ":", 2)
|
tokens := strings.SplitN(errorText, ":", 2)
|
||||||
if !strings.Contains(error, ":") || len(tokens) != 2 {
|
if !strings.Contains(errorText, ":") || len(tokens) != 2 {
|
||||||
return "Translate Error: " + error
|
return fmt.Sprintf("Translate error: the error text doesn't contain \":\", errorText = %s", errorText)
|
||||||
}
|
}
|
||||||
|
|
||||||
if langMap[lang] == nil {
|
if langMap[language] == nil {
|
||||||
file, _ := f.ReadFile("locales/" + lang + "/data.json")
|
file, err := f.ReadFile(fmt.Sprintf("locales/%s/data.json", language))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Sprintf("Translate error: the language \"%s\" is not supported, err = %s", language, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
data := I18nData{}
|
data := I18nData{}
|
||||||
err := util.JsonToStruct(string(file), &data)
|
err = util.JsonToStruct(string(file), &data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
langMap[lang] = data
|
langMap[language] = data
|
||||||
}
|
}
|
||||||
|
|
||||||
res := langMap[lang][tokens[0]][tokens[1]]
|
res := langMap[language][tokens[0]][tokens[1]]
|
||||||
if res == "" {
|
if res == "" {
|
||||||
res = tokens[1]
|
res = tokens[1]
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ package idp
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -83,7 +84,7 @@ func (idp *CasdoorIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
|||||||
|
|
||||||
// check if token is expired
|
// check if token is expired
|
||||||
if pToken.ExpiresIn <= 0 {
|
if pToken.ExpiresIn <= 0 {
|
||||||
return nil, fmt.Errorf("%s", pToken.AccessToken)
|
return nil, errors.New(pToken.AccessToken)
|
||||||
}
|
}
|
||||||
token := &oauth2.Token{
|
token := &oauth2.Token{
|
||||||
AccessToken: pToken.AccessToken,
|
AccessToken: pToken.AccessToken,
|
||||||
|
@ -179,8 +179,12 @@ func (idp *DingTalkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
corpEmail, jobNumber, err := idp.getUserCorpEmail(userId, corpAccessToken)
|
corpMobile, corpEmail, jobNumber, err := idp.getUserCorpEmail(userId, corpAccessToken)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
if corpMobile != "" {
|
||||||
|
userInfo.Phone = corpMobile
|
||||||
|
}
|
||||||
|
|
||||||
if corpEmail != "" {
|
if corpEmail != "" {
|
||||||
userInfo.Email = corpEmail
|
userInfo.Email = corpEmail
|
||||||
}
|
}
|
||||||
@ -264,27 +268,29 @@ func (idp *DingTalkIdProvider) getUserId(unionId string, accessToken string) (st
|
|||||||
return data.Result.UserId, nil
|
return data.Result.UserId, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (idp *DingTalkIdProvider) getUserCorpEmail(userId string, accessToken string) (string, string, error) {
|
func (idp *DingTalkIdProvider) getUserCorpEmail(userId string, accessToken string) (string, string, string, error) {
|
||||||
|
// https://open.dingtalk.com/document/isvapp/query-user-details
|
||||||
body := make(map[string]string)
|
body := make(map[string]string)
|
||||||
body["userid"] = userId
|
body["userid"] = userId
|
||||||
respBytes, err := idp.postWithBody(body, "https://oapi.dingtalk.com/topapi/v2/user/get?access_token="+accessToken)
|
respBytes, err := idp.postWithBody(body, "https://oapi.dingtalk.com/topapi/v2/user/get?access_token="+accessToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
var data struct {
|
var data struct {
|
||||||
ErrMessage string `json:"errmsg"`
|
ErrMessage string `json:"errmsg"`
|
||||||
Result struct {
|
Result struct {
|
||||||
|
Mobile string `json:"mobile"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
JobNumber string `json:"job_number"`
|
JobNumber string `json:"job_number"`
|
||||||
} `json:"result"`
|
} `json:"result"`
|
||||||
}
|
}
|
||||||
err = json.Unmarshal(respBytes, &data)
|
err = json.Unmarshal(respBytes, &data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", "", err
|
||||||
}
|
}
|
||||||
if data.ErrMessage != "ok" {
|
if data.ErrMessage != "ok" {
|
||||||
return "", "", fmt.Errorf(data.ErrMessage)
|
return "", "", "", fmt.Errorf(data.ErrMessage)
|
||||||
}
|
}
|
||||||
return data.Result.Email, data.Result.JobNumber, nil
|
return data.Result.Mobile, data.Result.Email, data.Result.JobNumber, nil
|
||||||
}
|
}
|
||||||
|
@ -75,7 +75,9 @@ func (idp *WeComIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
|||||||
ProviderSecret string `json:"provider_secret"`
|
ProviderSecret string `json:"provider_secret"`
|
||||||
}{idp.Config.ClientID, idp.Config.ClientSecret}
|
}{idp.Config.ClientID, idp.Config.ClientSecret}
|
||||||
data, err := idp.postWithBody(pTokenParams, "https://qyapi.weixin.qq.com/cgi-bin/service/get_provider_token")
|
data, err := idp.postWithBody(pTokenParams, "https://qyapi.weixin.qq.com/cgi-bin/service/get_provider_token")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
pToken := &WeComProviderToken{}
|
pToken := &WeComProviderToken{}
|
||||||
err = json.Unmarshal(data, pToken)
|
err = json.Unmarshal(data, pToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
3
main.go
3
main.go
@ -27,7 +27,6 @@ import (
|
|||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casdoor/casdoor/proxy"
|
"github.com/casdoor/casdoor/proxy"
|
||||||
"github.com/casdoor/casdoor/routers"
|
"github.com/casdoor/casdoor/routers"
|
||||||
_ "github.com/casdoor/casdoor/routers"
|
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -59,8 +58,8 @@ func main() {
|
|||||||
beego.InsertFilter("*", beego.BeforeRouter, routers.AutoSigninFilter)
|
beego.InsertFilter("*", beego.BeforeRouter, routers.AutoSigninFilter)
|
||||||
beego.InsertFilter("*", beego.BeforeRouter, routers.CorsFilter)
|
beego.InsertFilter("*", beego.BeforeRouter, routers.CorsFilter)
|
||||||
beego.InsertFilter("*", beego.BeforeRouter, routers.AuthzFilter)
|
beego.InsertFilter("*", beego.BeforeRouter, routers.AuthzFilter)
|
||||||
beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage)
|
|
||||||
beego.InsertFilter("*", beego.BeforeRouter, routers.PrometheusFilter)
|
beego.InsertFilter("*", beego.BeforeRouter, routers.PrometheusFilter)
|
||||||
|
beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage)
|
||||||
|
|
||||||
beego.BConfig.WebConfig.Session.SessionOn = true
|
beego.BConfig.WebConfig.Session.SessionOn = true
|
||||||
beego.BConfig.WebConfig.Session.SessionName = "casdoor_session_id"
|
beego.BConfig.WebConfig.Session.SessionName = "casdoor_session_id"
|
||||||
|
@ -240,6 +240,21 @@ func (a *Adapter) createTable() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = a.Engine.Sync2(new(Subscription))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.Engine.Sync2(new(Plan))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.Engine.Sync2(new(Pricing))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetSession(owner string, offset, limit int, field, value, sortField, sortOrder string) *xorm.Session {
|
func GetSession(owner string, offset, limit int, field, value, sortField, sortOrder string) *xorm.Session {
|
||||||
|
@ -38,7 +38,7 @@ type Application struct {
|
|||||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||||
|
|
||||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||||
Logo string `xorm:"varchar(100)" json:"logo"`
|
Logo string `xorm:"varchar(200)" json:"logo"`
|
||||||
HomepageUrl string `xorm:"varchar(100)" json:"homepageUrl"`
|
HomepageUrl string `xorm:"varchar(100)" json:"homepageUrl"`
|
||||||
Description string `xorm:"varchar(100)" json:"description"`
|
Description string `xorm:"varchar(100)" json:"description"`
|
||||||
Organization string `xorm:"varchar(100)" json:"organization"`
|
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||||
@ -51,6 +51,7 @@ type Application struct {
|
|||||||
EnableSamlCompress bool `json:"enableSamlCompress"`
|
EnableSamlCompress bool `json:"enableSamlCompress"`
|
||||||
EnableWebAuthn bool `json:"enableWebAuthn"`
|
EnableWebAuthn bool `json:"enableWebAuthn"`
|
||||||
EnableLinkWithEmail bool `json:"enableLinkWithEmail"`
|
EnableLinkWithEmail bool `json:"enableLinkWithEmail"`
|
||||||
|
OrgChoiceMode string `json:"orgChoiceMode"`
|
||||||
SamlReplyUrl string `xorm:"varchar(100)" json:"samlReplyUrl"`
|
SamlReplyUrl string `xorm:"varchar(100)" json:"samlReplyUrl"`
|
||||||
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
|
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
|
||||||
SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"`
|
SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"`
|
||||||
@ -145,12 +146,12 @@ func getProviderMap(owner string) map[string]*Provider {
|
|||||||
m := map[string]*Provider{}
|
m := map[string]*Provider{}
|
||||||
for _, provider := range providers {
|
for _, provider := range providers {
|
||||||
// Get QRCode only once
|
// Get QRCode only once
|
||||||
if provider.Type == "WeChat" && provider.DisableSsl == true && provider.Content == "" {
|
if provider.Type == "WeChat" && provider.DisableSsl && provider.Content == "" {
|
||||||
provider.Content, _ = idp.GetWechatOfficialAccountQRCode(provider.ClientId2, provider.ClientSecret2)
|
provider.Content, _ = idp.GetWechatOfficialAccountQRCode(provider.ClientId2, provider.ClientSecret2)
|
||||||
UpdateProvider(provider.Owner+"/"+provider.Name, provider)
|
UpdateProvider(provider.Owner+"/"+provider.Name, provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
m[provider.Name] = GetMaskedProvider(provider)
|
m[provider.Name] = GetMaskedProvider(provider, true)
|
||||||
}
|
}
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
@ -326,6 +327,12 @@ func UpdateApplication(id string, application *Application) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func AddApplication(application *Application) bool {
|
func AddApplication(application *Application) bool {
|
||||||
|
if application.Owner == "" {
|
||||||
|
application.Owner = "admin"
|
||||||
|
}
|
||||||
|
if application.Organization == "" {
|
||||||
|
application.Organization = "built-in"
|
||||||
|
}
|
||||||
if application.ClientId == "" {
|
if application.ClientId == "" {
|
||||||
application.ClientId = util.GenerateClientId()
|
application.ClientId = util.GenerateClientId()
|
||||||
}
|
}
|
||||||
|
@ -134,6 +134,24 @@ func getCert(owner string, name string) *Cert {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getCertByName(name string) *Cert {
|
||||||
|
if name == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cert := Cert{Name: name}
|
||||||
|
existed, err := adapter.Engine.Get(&cert)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
return &cert
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func GetCert(id string) *Cert {
|
func GetCert(id string) *Cert {
|
||||||
owner, name := util.GetOwnerAndNameFromId(id)
|
owner, name := util.GetOwnerAndNameFromId(id)
|
||||||
return getCert(owner, name)
|
return getCert(owner, name)
|
||||||
@ -189,7 +207,7 @@ func (p *Cert) GetId() string {
|
|||||||
|
|
||||||
func getCertByApplication(application *Application) *Cert {
|
func getCertByApplication(application *Application) *Cert {
|
||||||
if application.Cert != "" {
|
if application.Cert != "" {
|
||||||
return getCert("admin", application.Cert)
|
return getCertByName(application.Cert)
|
||||||
} else {
|
} else {
|
||||||
return GetDefaultCert()
|
return GetDefaultCert()
|
||||||
}
|
}
|
||||||
|
@ -211,7 +211,7 @@ func checkLdapUserPassword(user *User, password string, lang string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
searchReq := goldap.NewSearchRequest(ldapServer.BaseDn, goldap.ScopeWholeSubtree, goldap.NeverDerefAliases,
|
searchReq := goldap.NewSearchRequest(ldapServer.BaseDn, goldap.ScopeWholeSubtree, goldap.NeverDerefAliases,
|
||||||
0, 0, false, ldapServer.buildFilterString(user), []string{}, nil)
|
0, 0, false, ldapServer.buildAuthFilterString(user), []string{}, nil)
|
||||||
|
|
||||||
searchResult, err := conn.Conn.Search(searchReq)
|
searchResult, err := conn.Conn.Search(searchReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -248,7 +248,7 @@ func CheckUserPassword(organization string, username string, password string, la
|
|||||||
enableCaptcha = options[0]
|
enableCaptcha = options[0]
|
||||||
}
|
}
|
||||||
user := GetUserByFields(organization, username)
|
user := GetUserByFields(organization, username)
|
||||||
if user == nil || user.IsDeleted == true {
|
if user == nil || user.IsDeleted {
|
||||||
return nil, fmt.Sprintf(i18n.Translate(lang, "general:The user: %s doesn't exist"), util.GetId(organization, username))
|
return nil, fmt.Sprintf(i18n.Translate(lang, "general:The user: %s doesn't exist"), util.GetId(organization, username))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -321,6 +321,10 @@ func CheckUserPermission(requestUserId, userId string, strict bool, lang string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func CheckAccessPermission(userId string, application *Application) (bool, error) {
|
func CheckAccessPermission(userId string, application *Application) (bool, error) {
|
||||||
|
if userId == "built-in/admin" {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
permissions := GetPermissions(application.Organization)
|
permissions := GetPermissions(application.Organization)
|
||||||
allowed := true
|
allowed := true
|
||||||
var err error
|
var err error
|
||||||
|
@ -90,7 +90,7 @@ func initBuiltInOrganization() bool {
|
|||||||
CountryCodes: []string{"US", "ES", "CN", "FR", "DE", "GB", "JP", "KR", "VN", "ID", "SG", "IN"},
|
CountryCodes: []string{"US", "ES", "CN", "FR", "DE", "GB", "JP", "KR", "VN", "ID", "SG", "IN"},
|
||||||
DefaultAvatar: fmt.Sprintf("%s/img/casbin.svg", conf.GetConfigString("staticBaseUrl")),
|
DefaultAvatar: fmt.Sprintf("%s/img/casbin.svg", conf.GetConfigString("staticBaseUrl")),
|
||||||
Tags: []string{},
|
Tags: []string{},
|
||||||
Languages: []string{"en", "zh", "es", "fr", "de", "id", "ja", "ko", "ru", "vi"},
|
Languages: []string{"en", "zh", "es", "fr", "de", "id", "ja", "ko", "ru", "vi", "pt"},
|
||||||
InitScore: 2000,
|
InitScore: 2000,
|
||||||
AccountItems: getBuiltInAccountItems(),
|
AccountItems: getBuiltInAccountItems(),
|
||||||
EnableSoftDeletion: false,
|
EnableSoftDeletion: false,
|
||||||
|
@ -87,11 +87,13 @@ func (l *LdapAutoSynchronizer) syncRoutine(ldap *Ldap, stopChan chan struct{}) {
|
|||||||
logs.Warning(fmt.Sprintf("autoSync failed for %s, error %s", ldap.Id, err))
|
logs.Warning(fmt.Sprintf("autoSync failed for %s, error %s", ldap.Id, err))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
existed, failed := SyncLdapUsers(ldap.Owner, LdapUsersToLdapRespUsers(users), ldap.Id)
|
|
||||||
if len(*failed) != 0 {
|
existed, failed, err := SyncLdapUsers(ldap.Owner, AutoAdjustLdapUser(users), ldap.Id)
|
||||||
logs.Warning(fmt.Sprintf("ldap autosync,%d new users,but %d user failed during :", len(users)-len(*existed)-len(*failed), len(*failed)), *failed)
|
if len(failed) != 0 {
|
||||||
|
logs.Warning(fmt.Sprintf("ldap autosync,%d new users,but %d user failed during :", len(users)-len(existed)-len(failed), len(failed)), failed)
|
||||||
|
logs.Warning(err.Error())
|
||||||
} else {
|
} else {
|
||||||
logs.Info(fmt.Sprintf("ldap autosync success, %d new users, %d existing users", len(users)-len(*existed), len(*existed)))
|
logs.Info(fmt.Sprintf("ldap autosync success, %d new users, %d existing users", len(users)-len(existed), len(existed)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/beego/beego"
|
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
goldap "github.com/go-ldap/ldap/v3"
|
goldap "github.com/go-ldap/ldap/v3"
|
||||||
"github.com/thanhpk/randstr"
|
"github.com/thanhpk/randstr"
|
||||||
@ -35,35 +34,26 @@ type LdapConn struct {
|
|||||||
// Cn string
|
// Cn string
|
||||||
//}
|
//}
|
||||||
|
|
||||||
type ldapUser struct {
|
type LdapUser struct {
|
||||||
UidNumber string
|
UidNumber string `json:"uidNumber"`
|
||||||
Uid string
|
Uid string `json:"uid"`
|
||||||
Cn string
|
Cn string `json:"cn"`
|
||||||
GidNumber string
|
GidNumber string `json:"gidNumber"`
|
||||||
// Gcn string
|
// Gcn string
|
||||||
Uuid string
|
Uuid string `json:"uuid"`
|
||||||
DisplayName string
|
DisplayName string `json:"displayName"`
|
||||||
Mail string
|
Mail string
|
||||||
Email string
|
Email string `json:"email"`
|
||||||
EmailAddress string
|
EmailAddress string
|
||||||
TelephoneNumber string
|
TelephoneNumber string
|
||||||
Mobile string
|
Mobile string
|
||||||
MobileTelephoneNumber string
|
MobileTelephoneNumber string
|
||||||
RegisteredAddress string
|
RegisteredAddress string
|
||||||
PostalAddress string
|
PostalAddress string
|
||||||
}
|
|
||||||
|
|
||||||
type LdapRespUser struct {
|
GroupId string `json:"groupId"`
|
||||||
UidNumber string `json:"uidNumber"`
|
Phone string `json:"phone"`
|
||||||
Uid string `json:"uid"`
|
Address string `json:"address"`
|
||||||
Cn string `json:"cn"`
|
|
||||||
GroupId string `json:"groupId"`
|
|
||||||
// GroupName string `json:"groupName"`
|
|
||||||
Uuid string `json:"uuid"`
|
|
||||||
DisplayName string `json:"displayName"`
|
|
||||||
Email string `json:"email"`
|
|
||||||
Phone string `json:"phone"`
|
|
||||||
Address string `json:"address"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ldap *Ldap) GetLdapConn() (c *LdapConn, err error) {
|
func (ldap *Ldap) GetLdapConn() (c *LdapConn, err error) {
|
||||||
@ -136,7 +126,7 @@ func isMicrosoftAD(Conn *goldap.Conn) (bool, error) {
|
|||||||
return isMicrosoft, err
|
return isMicrosoft, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *LdapConn) GetLdapUsers(ldapServer *Ldap) ([]ldapUser, error) {
|
func (l *LdapConn) GetLdapUsers(ldapServer *Ldap) ([]LdapUser, error) {
|
||||||
SearchAttributes := []string{
|
SearchAttributes := []string{
|
||||||
"uidNumber", "cn", "sn", "gidNumber", "entryUUID", "displayName", "mail", "email",
|
"uidNumber", "cn", "sn", "gidNumber", "entryUUID", "displayName", "mail", "email",
|
||||||
"emailAddress", "telephoneNumber", "mobile", "mobileTelephoneNumber", "registeredAddress", "postalAddress",
|
"emailAddress", "telephoneNumber", "mobile", "mobileTelephoneNumber", "registeredAddress", "postalAddress",
|
||||||
@ -159,9 +149,9 @@ func (l *LdapConn) GetLdapUsers(ldapServer *Ldap) ([]ldapUser, error) {
|
|||||||
return nil, errors.New("no result")
|
return nil, errors.New("no result")
|
||||||
}
|
}
|
||||||
|
|
||||||
var ldapUsers []ldapUser
|
var ldapUsers []LdapUser
|
||||||
for _, entry := range searchResult.Entries {
|
for _, entry := range searchResult.Entries {
|
||||||
var user ldapUser
|
var user LdapUser
|
||||||
for _, attribute := range entry.Attributes {
|
for _, attribute := range entry.Attributes {
|
||||||
switch attribute.Name {
|
switch attribute.Name {
|
||||||
case "uidNumber":
|
case "uidNumber":
|
||||||
@ -241,35 +231,30 @@ func (l *LdapConn) GetLdapUsers(ldapServer *Ldap) ([]ldapUser, error) {
|
|||||||
// return groupMap, nil
|
// return groupMap, nil
|
||||||
// }
|
// }
|
||||||
|
|
||||||
func LdapUsersToLdapRespUsers(users []ldapUser) []LdapRespUser {
|
func AutoAdjustLdapUser(users []LdapUser) []LdapUser {
|
||||||
res := make([]LdapRespUser, 0)
|
res := make([]LdapUser, len(users))
|
||||||
for _, user := range users {
|
for i, user := range users {
|
||||||
res = append(res, LdapRespUser{
|
res[i] = LdapUser{
|
||||||
UidNumber: user.UidNumber,
|
UidNumber: user.UidNumber,
|
||||||
Uid: user.Uid,
|
Uid: user.Uid,
|
||||||
Cn: user.Cn,
|
Cn: user.Cn,
|
||||||
GroupId: user.GidNumber,
|
GroupId: user.GidNumber,
|
||||||
Uuid: user.Uuid,
|
Uuid: user.GetLdapUuid(),
|
||||||
DisplayName: user.DisplayName,
|
DisplayName: user.DisplayName,
|
||||||
Email: util.ReturnAnyNotEmpty(user.Email, user.EmailAddress, user.Mail),
|
Email: util.ReturnAnyNotEmpty(user.Email, user.EmailAddress, user.Mail),
|
||||||
Phone: util.ReturnAnyNotEmpty(user.Mobile, user.MobileTelephoneNumber, user.TelephoneNumber),
|
Mobile: util.ReturnAnyNotEmpty(user.Mobile, user.MobileTelephoneNumber, user.TelephoneNumber),
|
||||||
Address: util.ReturnAnyNotEmpty(user.PostalAddress, user.RegisteredAddress),
|
RegisteredAddress: util.ReturnAnyNotEmpty(user.PostalAddress, user.RegisteredAddress),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func SyncLdapUsers(owner string, respUsers []LdapRespUser, ldapId string) (*[]LdapRespUser, *[]LdapRespUser) {
|
func SyncLdapUsers(owner string, syncUsers []LdapUser, ldapId string) (existUsers []LdapUser, failedUsers []LdapUser, err error) {
|
||||||
var existUsers []LdapRespUser
|
|
||||||
var failedUsers []LdapRespUser
|
|
||||||
var uuids []string
|
var uuids []string
|
||||||
|
for _, user := range syncUsers {
|
||||||
for _, user := range respUsers {
|
|
||||||
uuids = append(uuids, user.Uuid)
|
uuids = append(uuids, user.Uuid)
|
||||||
}
|
}
|
||||||
|
|
||||||
existUuids := GetExistUuids(owner, uuids)
|
|
||||||
|
|
||||||
organization := getOrganization("admin", owner)
|
organization := getOrganization("admin", owner)
|
||||||
ldap := GetLdap(ldapId)
|
ldap := GetLdap(ldapId)
|
||||||
|
|
||||||
@ -289,67 +274,59 @@ func SyncLdapUsers(owner string, respUsers []LdapRespUser, ldapId string) (*[]Ld
|
|||||||
}
|
}
|
||||||
tag := strings.Join(ou, ".")
|
tag := strings.Join(ou, ".")
|
||||||
|
|
||||||
for _, respUser := range respUsers {
|
for _, syncUser := range syncUsers {
|
||||||
|
existUuids := GetExistUuids(owner, uuids)
|
||||||
found := false
|
found := false
|
||||||
if len(existUuids) > 0 {
|
if len(existUuids) > 0 {
|
||||||
for _, existUuid := range existUuids {
|
for _, existUuid := range existUuids {
|
||||||
if respUser.Uuid == existUuid {
|
if syncUser.Uuid == existUuid {
|
||||||
existUsers = append(existUsers, respUser)
|
existUsers = append(existUsers, syncUser)
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
|
score, _ := organization.GetInitScore()
|
||||||
newUser := &User{
|
newUser := &User{
|
||||||
Owner: owner,
|
Owner: owner,
|
||||||
Name: respUser.buildLdapUserName(),
|
Name: syncUser.buildLdapUserName(),
|
||||||
CreatedTime: util.GetCurrentTime(),
|
CreatedTime: util.GetCurrentTime(),
|
||||||
DisplayName: respUser.buildLdapDisplayName(),
|
DisplayName: syncUser.buildLdapDisplayName(),
|
||||||
Avatar: organization.DefaultAvatar,
|
Avatar: organization.DefaultAvatar,
|
||||||
Email: respUser.Email,
|
Email: syncUser.Email,
|
||||||
Phone: respUser.Phone,
|
Phone: syncUser.Phone,
|
||||||
Address: []string{respUser.Address},
|
Address: []string{syncUser.Address},
|
||||||
Affiliation: affiliation,
|
Affiliation: affiliation,
|
||||||
Tag: tag,
|
Tag: tag,
|
||||||
Score: beego.AppConfig.DefaultInt("initScore", 2000),
|
Score: score,
|
||||||
Ldap: respUser.Uuid,
|
Ldap: syncUser.Uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
affected := AddUser(newUser)
|
affected := AddUser(newUser)
|
||||||
if !affected {
|
if !affected {
|
||||||
failedUsers = append(failedUsers, respUser)
|
failedUsers = append(failedUsers, syncUser)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &existUsers, &failedUsers
|
return existUsers, failedUsers, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetExistUuids(owner string, uuids []string) []string {
|
func GetExistUuids(owner string, uuids []string) []string {
|
||||||
var users []User
|
|
||||||
var existUuids []string
|
var existUuids []string
|
||||||
existUuidSet := make(map[string]struct{})
|
|
||||||
|
|
||||||
err := adapter.Engine.Where(fmt.Sprintf("ldap IN (%s) AND owner = ?", "'"+strings.Join(uuids, "','")+"'"), owner).Find(&users)
|
err := adapter.Engine.Table("user").Where("owner = ?", owner).Cols("ldap").
|
||||||
|
In("ldap", uuids).Select("DISTINCT ldap").Find(&existUuids)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(users) > 0 {
|
|
||||||
for _, result := range users {
|
|
||||||
existUuidSet[result.Ldap] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for uuid := range existUuidSet {
|
|
||||||
existUuids = append(existUuids, uuid)
|
|
||||||
}
|
|
||||||
return existUuids
|
return existUuids
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ldapUser *LdapRespUser) buildLdapUserName() string {
|
func (ldapUser *LdapUser) buildLdapUserName() string {
|
||||||
user := User{}
|
user := User{}
|
||||||
uidWithNumber := fmt.Sprintf("%s_%s", ldapUser.Uid, ldapUser.UidNumber)
|
uidWithNumber := fmt.Sprintf("%s_%s", ldapUser.Uid, ldapUser.UidNumber)
|
||||||
has, err := adapter.Engine.Where("name = ? or name = ?", ldapUser.Uid, uidWithNumber).Get(&user)
|
has, err := adapter.Engine.Where("name = ? or name = ?", ldapUser.Uid, uidWithNumber).Get(&user)
|
||||||
@ -364,10 +341,14 @@ func (ldapUser *LdapRespUser) buildLdapUserName() string {
|
|||||||
return fmt.Sprintf("%s_%s", uidWithNumber, randstr.Hex(6))
|
return fmt.Sprintf("%s_%s", uidWithNumber, randstr.Hex(6))
|
||||||
}
|
}
|
||||||
|
|
||||||
return ldapUser.Uid
|
if ldapUser.Uid != "" {
|
||||||
|
return ldapUser.Uid
|
||||||
|
}
|
||||||
|
|
||||||
|
return ldapUser.Cn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ldapUser *LdapRespUser) buildLdapDisplayName() string {
|
func (ldapUser *LdapUser) buildLdapDisplayName() string {
|
||||||
if ldapUser.DisplayName != "" {
|
if ldapUser.DisplayName != "" {
|
||||||
return ldapUser.DisplayName
|
return ldapUser.DisplayName
|
||||||
}
|
}
|
||||||
@ -375,7 +356,18 @@ func (ldapUser *LdapRespUser) buildLdapDisplayName() string {
|
|||||||
return ldapUser.Cn
|
return ldapUser.Cn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ldap *Ldap) buildFilterString(user *User) string {
|
func (ldapUser *LdapUser) GetLdapUuid() string {
|
||||||
|
if ldapUser.Uuid != "" {
|
||||||
|
return ldapUser.Uuid
|
||||||
|
}
|
||||||
|
if ldapUser.Uid != "" {
|
||||||
|
return ldapUser.Uid
|
||||||
|
}
|
||||||
|
|
||||||
|
return ldapUser.Cn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ldap *Ldap) buildAuthFilterString(user *User) string {
|
||||||
if len(ldap.FilterFields) == 0 {
|
if len(ldap.FilterFields) == 0 {
|
||||||
return fmt.Sprintf("(&%s(uid=%s))", ldap.Filter, user.Name)
|
return fmt.Sprintf("(&%s(uid=%s))", ldap.Filter, user.Name)
|
||||||
}
|
}
|
||||||
@ -393,6 +385,8 @@ func (user *User) getFieldFromLdapAttribute(attribute string) string {
|
|||||||
switch attribute {
|
switch attribute {
|
||||||
case "uid":
|
case "uid":
|
||||||
return user.Name
|
return user.Name
|
||||||
|
case "sAMAccountName":
|
||||||
|
return user.Name
|
||||||
case "mail":
|
case "mail":
|
||||||
return user.Email
|
return user.Email
|
||||||
case "mobile":
|
case "mobile":
|
||||||
|
@ -51,6 +51,7 @@ const (
|
|||||||
const (
|
const (
|
||||||
MfaSessionUserId = "MfaSessionUserId"
|
MfaSessionUserId = "MfaSessionUserId"
|
||||||
NextMfa = "NextMfa"
|
NextMfa = "NextMfa"
|
||||||
|
RequiredMfa = "RequiredMfa"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetMfaUtil(providerType string, config *MfaProps) MfaInterface {
|
func GetMfaUtil(providerType string, config *MfaProps) MfaInterface {
|
||||||
|
@ -25,10 +25,7 @@ type Migrator_1_235_0_PR_1530 struct{}
|
|||||||
func (*Migrator_1_235_0_PR_1530) IsMigrationNeeded() bool {
|
func (*Migrator_1_235_0_PR_1530) IsMigrationNeeded() bool {
|
||||||
exist, _ := adapter.Engine.IsTableExist("casbin_rule")
|
exist, _ := adapter.Engine.IsTableExist("casbin_rule")
|
||||||
|
|
||||||
if exist {
|
return exist
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*Migrator_1_235_0_PR_1530) DoMigration() *migrate.Migration {
|
func (*Migrator_1_235_0_PR_1530) DoMigration() *migrate.Migration {
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
package object
|
package object
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/xorm-io/core"
|
|
||||||
"github.com/xorm-io/xorm"
|
"github.com/xorm-io/xorm"
|
||||||
"github.com/xorm-io/xorm/migrate"
|
"github.com/xorm-io/xorm/migrate"
|
||||||
)
|
)
|
||||||
@ -52,32 +51,15 @@ func (*Migrator_1_314_0_PR_1841) DoMigration() *migrate.Migration {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
users := []*User{}
|
|
||||||
organizations := []*Organization{}
|
organizations := []*Organization{}
|
||||||
|
|
||||||
err = tx.Table("user").Find(&users)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = tx.Table("organization").Find(&organizations)
|
err = tx.Table("organization").Find(&organizations)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
passwordTypes := make(map[string]string)
|
for _, organization := range organizations {
|
||||||
for _, org := range organizations {
|
user := &User{PasswordType: organization.PasswordType}
|
||||||
passwordTypes[org.Name] = org.PasswordType
|
_, err = tx.Where("owner = ?", organization.Name).Cols("password_type").Update(user)
|
||||||
}
|
|
||||||
|
|
||||||
columns := []string{
|
|
||||||
"password_type",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, u := range users {
|
|
||||||
u.PasswordType = passwordTypes[u.Owner]
|
|
||||||
|
|
||||||
_, err := tx.ID(core.PK{u.Owner, u.Name}).Cols(columns...).Update(u)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -55,13 +55,8 @@ func isIpAddress(host string) bool {
|
|||||||
|
|
||||||
// Attempt to parse the host as an IP address (both IPv4 and IPv6)
|
// Attempt to parse the host as an IP address (both IPv4 and IPv6)
|
||||||
ip := net.ParseIP(hostWithoutPort)
|
ip := net.ParseIP(hostWithoutPort)
|
||||||
if ip != nil {
|
// if host is not nil is an IP address else is not an IP address
|
||||||
// The host is an IP address
|
return ip != nil
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// The host is not an IP address
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getOriginFromHost(host string) (string, string) {
|
func getOriginFromHost(host string) (string, string) {
|
||||||
|
@ -16,7 +16,9 @@ package object
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/conf"
|
||||||
"github.com/casdoor/casdoor/cred"
|
"github.com/casdoor/casdoor/cred"
|
||||||
"github.com/casdoor/casdoor/i18n"
|
"github.com/casdoor/casdoor/i18n"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
@ -38,6 +40,11 @@ type ThemeData struct {
|
|||||||
IsEnabled bool `xorm:"bool" json:"isEnabled"`
|
IsEnabled bool `xorm:"bool" json:"isEnabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MfaItem struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Rule string `json:"rule"`
|
||||||
|
}
|
||||||
|
|
||||||
type Organization struct {
|
type Organization struct {
|
||||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||||
@ -59,6 +66,7 @@ type Organization struct {
|
|||||||
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
||||||
IsProfilePublic bool `json:"isProfilePublic"`
|
IsProfilePublic bool `json:"isProfilePublic"`
|
||||||
|
|
||||||
|
MfaItems []*MfaItem `xorm:"varchar(300)" json:"mfaItems"`
|
||||||
AccountItems []*AccountItem `xorm:"varchar(3000)" json:"accountItems"`
|
AccountItems []*AccountItem `xorm:"varchar(3000)" json:"accountItems"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,6 +90,16 @@ func GetOrganizations(owner string) []*Organization {
|
|||||||
return organizations
|
return organizations
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetOrganizationsByFields(owner string, fields ...string) []*Organization {
|
||||||
|
organizations := []*Organization{}
|
||||||
|
err := adapter.Engine.Desc("created_time").Cols(fields...).Find(&organizations, &Organization{Owner: owner})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return organizations
|
||||||
|
}
|
||||||
|
|
||||||
func GetPaginationOrganizations(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Organization {
|
func GetPaginationOrganizations(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Organization {
|
||||||
organizations := []*Organization{}
|
organizations := []*Organization{}
|
||||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||||
@ -408,3 +426,20 @@ func organizationChangeTrigger(oldName string, newName string) error {
|
|||||||
|
|
||||||
return session.Commit()
|
return session.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (org *Organization) HasRequiredMfa() bool {
|
||||||
|
for _, item := range org.MfaItems {
|
||||||
|
if item.Rule == "Required" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (org *Organization) GetInitScore() (int, error) {
|
||||||
|
if org != nil {
|
||||||
|
return org.InitScore, nil
|
||||||
|
} else {
|
||||||
|
return strconv.Atoi(conf.GetConfigString("initScore"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -152,46 +152,47 @@ func DeletePayment(payment *Payment) bool {
|
|||||||
return affected != 0
|
return affected != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func notifyPayment(request *http.Request, body []byte, owner string, providerName string, productName string, paymentName string) (*Payment, error) {
|
func notifyPayment(request *http.Request, body []byte, owner string, providerName string, productName string, paymentName string) (*Payment, error, string) {
|
||||||
|
provider := getProvider(owner, providerName)
|
||||||
|
|
||||||
|
pProvider, cert, err := provider.getPaymentProvider()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
payment := getPayment(owner, paymentName)
|
payment := getPayment(owner, paymentName)
|
||||||
if payment == nil {
|
if payment == nil {
|
||||||
return nil, fmt.Errorf("the payment: %s does not exist", paymentName)
|
err = fmt.Errorf("the payment: %s does not exist", paymentName)
|
||||||
|
return nil, err, pProvider.GetResponseError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
product := getProduct(owner, productName)
|
product := getProduct(owner, productName)
|
||||||
if product == nil {
|
if product == nil {
|
||||||
return nil, fmt.Errorf("the product: %s does not exist", productName)
|
err = fmt.Errorf("the product: %s does not exist", productName)
|
||||||
}
|
return payment, err, pProvider.GetResponseError(err)
|
||||||
|
|
||||||
provider, err := product.getProvider(providerName)
|
|
||||||
if err != nil {
|
|
||||||
return payment, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pProvider, cert, err := provider.getPaymentProvider()
|
|
||||||
if err != nil {
|
|
||||||
return payment, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
productDisplayName, paymentName, price, productName, providerName, err := pProvider.Notify(request, body, cert.AuthorityPublicKey)
|
productDisplayName, paymentName, price, productName, providerName, err := pProvider.Notify(request, body, cert.AuthorityPublicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return payment, err
|
return payment, err, pProvider.GetResponseError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if productDisplayName != "" && productDisplayName != product.DisplayName {
|
if productDisplayName != "" && productDisplayName != product.DisplayName {
|
||||||
return nil, fmt.Errorf("the payment's product name: %s doesn't equal to the expected product name: %s", productDisplayName, product.DisplayName)
|
err = fmt.Errorf("the payment's product name: %s doesn't equal to the expected product name: %s", productDisplayName, product.DisplayName)
|
||||||
|
return payment, err, pProvider.GetResponseError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if price != product.Price {
|
if price != product.Price {
|
||||||
return nil, fmt.Errorf("the payment's price: %f doesn't equal to the expected price: %f", price, product.Price)
|
err = fmt.Errorf("the payment's price: %f doesn't equal to the expected price: %f", price, product.Price)
|
||||||
|
return payment, err, pProvider.GetResponseError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return payment, nil
|
err = nil
|
||||||
|
return payment, err, pProvider.GetResponseError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NotifyPayment(request *http.Request, body []byte, owner string, providerName string, productName string, paymentName string) bool {
|
func NotifyPayment(request *http.Request, body []byte, owner string, providerName string, productName string, paymentName string) (error, string) {
|
||||||
payment, err := notifyPayment(request, body, owner, providerName, productName, paymentName)
|
payment, err, errorResponse := notifyPayment(request, body, owner, providerName, productName, paymentName)
|
||||||
|
|
||||||
if payment != nil {
|
if payment != nil {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
payment.State = "Error"
|
payment.State = "Error"
|
||||||
@ -203,8 +204,7 @@ func NotifyPayment(request *http.Request, body []byte, owner string, providerNam
|
|||||||
UpdatePayment(payment.GetId(), payment)
|
UpdatePayment(payment.GetId(), payment)
|
||||||
}
|
}
|
||||||
|
|
||||||
ok := err == nil
|
return err, errorResponse
|
||||||
return ok
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func invoicePayment(payment *Payment) (string, error) {
|
func invoicePayment(payment *Payment) (string, error) {
|
||||||
|
@ -15,6 +15,9 @@
|
|||||||
package object
|
package object
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/conf"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"github.com/xorm-io/core"
|
"github.com/xorm-io/core"
|
||||||
)
|
)
|
||||||
@ -188,6 +191,54 @@ func AddPermission(permission *Permission) bool {
|
|||||||
return affected != 0
|
return affected != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func AddPermissions(permissions []*Permission) bool {
|
||||||
|
if len(permissions) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
affected, err := adapter.Engine.Insert(permissions)
|
||||||
|
if err != nil {
|
||||||
|
if !strings.Contains(err.Error(), "Duplicate entry") {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, permission := range permissions {
|
||||||
|
// add using for loop
|
||||||
|
if affected != 0 {
|
||||||
|
addGroupingPolicies(permission)
|
||||||
|
addPolicies(permission)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddPermissionsInBatch(permissions []*Permission) bool {
|
||||||
|
batchSize := conf.GetConfigBatchSize()
|
||||||
|
|
||||||
|
if len(permissions) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
affected := false
|
||||||
|
for i := 0; i < (len(permissions)-1)/batchSize+1; i++ {
|
||||||
|
start := i * batchSize
|
||||||
|
end := (i + 1) * batchSize
|
||||||
|
if end > len(permissions) {
|
||||||
|
end = len(permissions)
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp := permissions[start:end]
|
||||||
|
// TODO: save to log instead of standard output
|
||||||
|
// fmt.Printf("Add Permissions: [%d - %d].\n", start, end)
|
||||||
|
if AddPermissions(tmp) {
|
||||||
|
affected = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected
|
||||||
|
}
|
||||||
|
|
||||||
func DeletePermission(permission *Permission) bool {
|
func DeletePermission(permission *Permission) bool {
|
||||||
affected, err := adapter.Engine.ID(core.PK{permission.Owner, permission.Name}).Delete(&Permission{})
|
affected, err := adapter.Engine.ID(core.PK{permission.Owner, permission.Name}).Delete(&Permission{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -235,6 +286,16 @@ func GetPermissionsByRole(roleId string) []*Permission {
|
|||||||
return permissions
|
return permissions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetPermissionsByResource(resourceId string) []*Permission {
|
||||||
|
permissions := []*Permission{}
|
||||||
|
err := adapter.Engine.Where("resources like ?", "%"+resourceId+"\"%").Find(&permissions)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return permissions
|
||||||
|
}
|
||||||
|
|
||||||
func GetPermissionsBySubmitter(owner string, submitter string) []*Permission {
|
func GetPermissionsBySubmitter(owner string, submitter string) []*Permission {
|
||||||
permissions := []*Permission{}
|
permissions := []*Permission{}
|
||||||
err := adapter.Engine.Desc("created_time").Find(&permissions, &Permission{Owner: owner, Submitter: submitter})
|
err := adapter.Engine.Desc("created_time").Find(&permissions, &Permission{Owner: owner, Submitter: submitter})
|
||||||
|
77
object/permission_upload.go
Normal file
77
object/permission_upload.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
// 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 object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/casdoor/casdoor/xlsx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getPermissionMap(owner string) map[string]*Permission {
|
||||||
|
m := map[string]*Permission{}
|
||||||
|
|
||||||
|
permissions := GetPermissions(owner)
|
||||||
|
for _, permission := range permissions {
|
||||||
|
m[permission.GetId()] = permission
|
||||||
|
}
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func UploadPermissions(owner string, fileId string) bool {
|
||||||
|
table := xlsx.ReadXlsxFile(fileId)
|
||||||
|
|
||||||
|
oldUserMap := getPermissionMap(owner)
|
||||||
|
newPermissions := []*Permission{}
|
||||||
|
for index, line := range table {
|
||||||
|
if index == 0 || parseLineItem(&line, 0) == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
permission := &Permission{
|
||||||
|
Owner: parseLineItem(&line, 0),
|
||||||
|
Name: parseLineItem(&line, 1),
|
||||||
|
CreatedTime: parseLineItem(&line, 2),
|
||||||
|
DisplayName: parseLineItem(&line, 3),
|
||||||
|
|
||||||
|
Users: parseListItem(&line, 4),
|
||||||
|
Roles: parseListItem(&line, 5),
|
||||||
|
Domains: parseListItem(&line, 6),
|
||||||
|
|
||||||
|
Model: parseLineItem(&line, 7),
|
||||||
|
Adapter: parseLineItem(&line, 8),
|
||||||
|
ResourceType: parseLineItem(&line, 9),
|
||||||
|
|
||||||
|
Resources: parseListItem(&line, 10),
|
||||||
|
Actions: parseListItem(&line, 11),
|
||||||
|
|
||||||
|
Effect: parseLineItem(&line, 12),
|
||||||
|
IsEnabled: parseLineItemBool(&line, 13),
|
||||||
|
|
||||||
|
Submitter: parseLineItem(&line, 14),
|
||||||
|
Approver: parseLineItem(&line, 15),
|
||||||
|
ApproveTime: parseLineItem(&line, 16),
|
||||||
|
State: parseLineItem(&line, 17),
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := oldUserMap[permission.GetId()]; !ok {
|
||||||
|
newPermissions = append(newPermissions, permission)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(newPermissions) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return AddPermissionsInBatch(newPermissions)
|
||||||
|
}
|
145
object/plan.go
Normal file
145
object/plan.go
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
// 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 object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
"github.com/xorm-io/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Plan 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"`
|
||||||
|
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"`
|
||||||
|
|
||||||
|
Role string `xorm:"varchar(100)" json:"role"`
|
||||||
|
Options []string `xorm:"-" json:"options"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPlanCount(owner, field, value string) int {
|
||||||
|
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||||
|
count, err := session.Count(&Plan{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPlans(owner string) []*Plan {
|
||||||
|
plans := []*Plan{}
|
||||||
|
err := adapter.Engine.Desc("created_time").Find(&plans, &Plan{Owner: owner})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return plans
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPaginatedPlans(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Plan {
|
||||||
|
plans := []*Plan{}
|
||||||
|
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||||
|
err := session.Find(&plans)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return plans
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPlan(owner, name string) *Plan {
|
||||||
|
if owner == "" || name == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
plan := Plan{Owner: owner, Name: name}
|
||||||
|
existed, err := adapter.Engine.Get(&plan)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if existed {
|
||||||
|
return &plan
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPlan(id string) *Plan {
|
||||||
|
owner, name := util.GetOwnerAndNameFromId(id)
|
||||||
|
return getPlan(owner, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdatePlan(id string, plan *Plan) bool {
|
||||||
|
owner, name := util.GetOwnerAndNameFromId(id)
|
||||||
|
if getPlan(owner, name) == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(plan)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddPlan(plan *Plan) bool {
|
||||||
|
affected, err := adapter.Engine.Insert(plan)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeletePlan(plan *Plan) bool {
|
||||||
|
affected, err := adapter.Engine.ID(core.PK{plan.Owner, plan.Name}).Delete(plan)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
selectedPricing := GetPricing(fmt.Sprintf("%s/%s", owner, pricing))
|
||||||
|
|
||||||
|
valid := selectedPricing != nil && selectedPricing.IsEnabled
|
||||||
|
|
||||||
|
if !valid {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
planBelongToPricing := selectedPricing.HasPlan(owner, plan)
|
||||||
|
|
||||||
|
if planBelongToPricing {
|
||||||
|
newSubscription := NewSubscription(owner, user, plan, selectedPricing.TrialDuration)
|
||||||
|
affected := AddSubscription(newSubscription)
|
||||||
|
|
||||||
|
if affected {
|
||||||
|
return newSubscription
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
147
object/pricing.go
Normal file
147
object/pricing.go
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
// 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 object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
"github.com/xorm-io/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Pricing 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"`
|
||||||
|
Description string `xorm:"varchar(100)" json:"description"`
|
||||||
|
|
||||||
|
Plans []string `xorm:"mediumtext" json:"plans"`
|
||||||
|
IsEnabled bool `json:"isEnabled"`
|
||||||
|
HasTrial bool `json:"hasTrial"`
|
||||||
|
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"`
|
||||||
|
|
||||||
|
State string `xorm:"varchar(100)" json:"state"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPricingCount(owner, field, value string) int {
|
||||||
|
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||||
|
count, err := session.Count(&Pricing{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPricings(owner string) []*Pricing {
|
||||||
|
pricings := []*Pricing{}
|
||||||
|
err := adapter.Engine.Desc("created_time").Find(&pricings, &Pricing{Owner: owner})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return pricings
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPaginatedPricings(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Pricing {
|
||||||
|
pricings := []*Pricing{}
|
||||||
|
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||||
|
err := session.Find(&pricings)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return pricings
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPricing(owner, name string) *Pricing {
|
||||||
|
if owner == "" || name == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pricing := Pricing{Owner: owner, Name: name}
|
||||||
|
existed, err := adapter.Engine.Get(&pricing)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if existed {
|
||||||
|
return &pricing
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPricing(id string) *Pricing {
|
||||||
|
owner, name := util.GetOwnerAndNameFromId(id)
|
||||||
|
return getPricing(owner, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdatePricing(id string, pricing *Pricing) bool {
|
||||||
|
owner, name := util.GetOwnerAndNameFromId(id)
|
||||||
|
if getPricing(owner, name) == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(pricing)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddPricing(pricing *Pricing) bool {
|
||||||
|
affected, err := adapter.Engine.Insert(pricing)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeletePricing(pricing *Pricing) bool {
|
||||||
|
affected, err := adapter.Engine.ID(core.PK{pricing.Owner, pricing.Name}).Delete(pricing)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pricing *Pricing) GetId() string {
|
||||||
|
return fmt.Sprintf("%s/%s", pricing.Owner, pricing.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pricing *Pricing) HasPlan(owner string, plan string) bool {
|
||||||
|
selectedPlan := GetPlan(fmt.Sprintf("%s/%s", owner, plan))
|
||||||
|
|
||||||
|
if selectedPlan == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
result := false
|
||||||
|
|
||||||
|
for _, pricingPlan := range pricing.Plans {
|
||||||
|
if strings.Contains(pricingPlan, selectedPlan.Name) {
|
||||||
|
result = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
@ -70,7 +70,11 @@ type Provider struct {
|
|||||||
ProviderUrl string `xorm:"varchar(200)" json:"providerUrl"`
|
ProviderUrl string `xorm:"varchar(200)" json:"providerUrl"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetMaskedProvider(provider *Provider) *Provider {
|
func GetMaskedProvider(provider *Provider, isMaskEnabled bool) *Provider {
|
||||||
|
if !isMaskEnabled {
|
||||||
|
return provider
|
||||||
|
}
|
||||||
|
|
||||||
if provider == nil {
|
if provider == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -88,9 +92,13 @@ func GetMaskedProvider(provider *Provider) *Provider {
|
|||||||
return provider
|
return provider
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetMaskedProviders(providers []*Provider) []*Provider {
|
func GetMaskedProviders(providers []*Provider, isMaskEnabled bool) []*Provider {
|
||||||
|
if !isMaskEnabled {
|
||||||
|
return providers
|
||||||
|
}
|
||||||
|
|
||||||
for _, provider := range providers {
|
for _, provider := range providers {
|
||||||
provider = GetMaskedProvider(provider)
|
provider = GetMaskedProvider(provider, isMaskEnabled)
|
||||||
}
|
}
|
||||||
return providers
|
return providers
|
||||||
}
|
}
|
||||||
@ -310,7 +318,7 @@ func GetCaptchaProviderByApplication(applicationId, isCurrentProvider, lang stri
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if provider.Provider.Category == "Captcha" {
|
if provider.Provider.Category == "Captcha" {
|
||||||
return GetCaptchaProviderByOwnerName(fmt.Sprintf("%s/%s", provider.Provider.Owner, provider.Provider.Name), lang)
|
return GetCaptchaProviderByOwnerName(util.GetId(provider.Provider.Owner, provider.Provider.Name), lang)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
@ -47,7 +47,8 @@ type Record struct {
|
|||||||
RequestUri string `xorm:"varchar(1000)" json:"requestUri"`
|
RequestUri string `xorm:"varchar(1000)" json:"requestUri"`
|
||||||
Action string `xorm:"varchar(1000)" json:"action"`
|
Action string `xorm:"varchar(1000)" json:"action"`
|
||||||
|
|
||||||
ExtendedUser *User `xorm:"-" json:"extendedUser"`
|
Object string `xorm:"-" json:"object"`
|
||||||
|
ExtendedUser *User `xorm:"-" json:"extendedUser"`
|
||||||
|
|
||||||
IsTriggered bool `json:"isTriggered"`
|
IsTriggered bool `json:"isTriggered"`
|
||||||
}
|
}
|
||||||
@ -60,6 +61,11 @@ func NewRecord(ctx *context.Context) *Record {
|
|||||||
requestUri = requestUri[0:1000]
|
requestUri = requestUri[0:1000]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object := ""
|
||||||
|
if ctx.Input.RequestBody != nil && len(ctx.Input.RequestBody) != 0 {
|
||||||
|
object = string(ctx.Input.RequestBody)
|
||||||
|
}
|
||||||
|
|
||||||
record := Record{
|
record := Record{
|
||||||
Name: util.GenerateId(),
|
Name: util.GenerateId(),
|
||||||
CreatedTime: util.GetCurrentTime(),
|
CreatedTime: util.GetCurrentTime(),
|
||||||
@ -68,6 +74,7 @@ func NewRecord(ctx *context.Context) *Record {
|
|||||||
Method: ctx.Request.Method,
|
Method: ctx.Request.Method,
|
||||||
RequestUri: requestUri,
|
RequestUri: requestUri,
|
||||||
Action: action,
|
Action: action,
|
||||||
|
Object: object,
|
||||||
IsTriggered: false,
|
IsTriggered: false,
|
||||||
}
|
}
|
||||||
return &record
|
return &record
|
||||||
@ -159,7 +166,7 @@ func SendWebhooks(record *Record) error {
|
|||||||
|
|
||||||
if matched {
|
if matched {
|
||||||
if webhook.IsUserExtended {
|
if webhook.IsUserExtended {
|
||||||
user := getUser(record.Organization, record.User)
|
user := GetMaskedUser(getUser(record.Organization, record.User))
|
||||||
record.ExtendedUser = user
|
record.ExtendedUser = user
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,9 @@ package object
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/conf"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"github.com/xorm-io/core"
|
"github.com/xorm-io/core"
|
||||||
@ -160,6 +163,45 @@ func AddRole(role *Role) bool {
|
|||||||
return affected != 0
|
return affected != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func AddRoles(roles []*Role) bool {
|
||||||
|
if len(roles) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
affected, err := adapter.Engine.Insert(roles)
|
||||||
|
if err != nil {
|
||||||
|
if !strings.Contains(err.Error(), "Duplicate entry") {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddRolesInBatch(roles []*Role) bool {
|
||||||
|
batchSize := conf.GetConfigBatchSize()
|
||||||
|
|
||||||
|
if len(roles) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
affected := false
|
||||||
|
for i := 0; i < (len(roles)-1)/batchSize+1; i++ {
|
||||||
|
start := i * batchSize
|
||||||
|
end := (i + 1) * batchSize
|
||||||
|
if end > len(roles) {
|
||||||
|
end = len(roles)
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp := roles[start:end]
|
||||||
|
// TODO: save to log instead of standard output
|
||||||
|
// fmt.Printf("Add users: [%d - %d].\n", start, end)
|
||||||
|
if AddRoles(tmp) {
|
||||||
|
affected = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected
|
||||||
|
}
|
||||||
|
|
||||||
func DeleteRole(role *Role) bool {
|
func DeleteRole(role *Role) bool {
|
||||||
roleId := role.GetId()
|
roleId := role.GetId()
|
||||||
permissions := GetPermissionsByRole(roleId)
|
permissions := GetPermissionsByRole(roleId)
|
||||||
|
63
object/role_upload.go
Normal file
63
object/role_upload.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// 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 object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/casdoor/casdoor/xlsx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getRoleMap(owner string) map[string]*Role {
|
||||||
|
m := map[string]*Role{}
|
||||||
|
|
||||||
|
roles := GetRoles(owner)
|
||||||
|
for _, role := range roles {
|
||||||
|
m[role.GetId()] = role
|
||||||
|
}
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func UploadRoles(owner string, fileId string) bool {
|
||||||
|
table := xlsx.ReadXlsxFile(fileId)
|
||||||
|
|
||||||
|
oldUserMap := getRoleMap(owner)
|
||||||
|
newRoles := []*Role{}
|
||||||
|
for index, line := range table {
|
||||||
|
if index == 0 || parseLineItem(&line, 0) == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
role := &Role{
|
||||||
|
Owner: parseLineItem(&line, 0),
|
||||||
|
Name: parseLineItem(&line, 1),
|
||||||
|
CreatedTime: parseLineItem(&line, 2),
|
||||||
|
DisplayName: parseLineItem(&line, 3),
|
||||||
|
|
||||||
|
Users: parseListItem(&line, 4),
|
||||||
|
Roles: parseListItem(&line, 5),
|
||||||
|
Domains: parseListItem(&line, 6),
|
||||||
|
IsEnabled: parseLineItemBool(&line, 7),
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := oldUserMap[role.GetId()]; !ok {
|
||||||
|
newRoles = append(newRoles, role)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(newRoles) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return AddRolesInBatch(newRoles)
|
||||||
|
}
|
154
object/subscription.go
Normal file
154
object/subscription.go
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
// 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 object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
"github.com/xorm-io/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultStatus = "Pending"
|
||||||
|
|
||||||
|
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"`
|
||||||
|
Duration int `json:"duration"`
|
||||||
|
|
||||||
|
Description string `xorm:"varchar(100)" json:"description"`
|
||||||
|
Plan string `xorm:"varchar(100)" json:"plan"`
|
||||||
|
|
||||||
|
StartDate time.Time `json:"startDate"`
|
||||||
|
EndDate time.Time `json:"endDate"`
|
||||||
|
|
||||||
|
User string `xorm:"mediumtext" json:"user"`
|
||||||
|
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSubscription(owner string, user string, plan string, duration int) *Subscription {
|
||||||
|
id := util.GenerateId()[:6]
|
||||||
|
return &Subscription{
|
||||||
|
Name: "Subscription_" + id,
|
||||||
|
DisplayName: "New Subscription - " + id,
|
||||||
|
Owner: owner,
|
||||||
|
User: owner + "/" + user,
|
||||||
|
Plan: owner + "/" + plan,
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
State: defaultStatus,
|
||||||
|
Duration: duration,
|
||||||
|
StartDate: time.Now(),
|
||||||
|
EndDate: time.Now().AddDate(0, 0, duration),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSubscriptionCount(owner, field, value string) int {
|
||||||
|
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||||
|
count, err := session.Count(&Subscription{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSubscriptions(owner string) []*Subscription {
|
||||||
|
subscriptions := []*Subscription{}
|
||||||
|
err := adapter.Engine.Desc("created_time").Find(&subscriptions, &Subscription{Owner: owner})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return subscriptions
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPaginationSubscriptions(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Subscription {
|
||||||
|
subscriptions := []*Subscription{}
|
||||||
|
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||||
|
err := session.Find(&subscriptions)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return subscriptions
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSubscription(owner string, name string) *Subscription {
|
||||||
|
if owner == "" || name == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
subscription := Subscription{Owner: owner, Name: name}
|
||||||
|
existed, err := adapter.Engine.Get(&subscription)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
return &subscription
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSubscription(id string) *Subscription {
|
||||||
|
owner, name := util.GetOwnerAndNameFromId(id)
|
||||||
|
return getSubscription(owner, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateSubscription(id string, subscription *Subscription) bool {
|
||||||
|
owner, name := util.GetOwnerAndNameFromId(id)
|
||||||
|
if getSubscription(owner, name) == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(subscription)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddSubscription(subscription *Subscription) bool {
|
||||||
|
affected, err := adapter.Engine.Insert(subscription)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteSubscription(subscription *Subscription) bool {
|
||||||
|
affected, err := adapter.Engine.ID(core.PK{subscription.Owner, subscription.Name}).Delete(&Subscription{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (subscription *Subscription) GetId() string {
|
||||||
|
return fmt.Sprintf("%s/%s", subscription.Owner, subscription.Name)
|
||||||
|
}
|
@ -224,13 +224,16 @@ func generateJwtToken(application *Application, user *User, nonce string, scope
|
|||||||
nowTime := time.Now()
|
nowTime := time.Now()
|
||||||
expireTime := nowTime.Add(time.Duration(application.ExpireInHours) * time.Hour)
|
expireTime := nowTime.Add(time.Duration(application.ExpireInHours) * time.Hour)
|
||||||
refreshExpireTime := nowTime.Add(time.Duration(application.RefreshExpireInHours) * time.Hour)
|
refreshExpireTime := nowTime.Add(time.Duration(application.RefreshExpireInHours) * time.Hour)
|
||||||
|
if application.RefreshExpireInHours == 0 {
|
||||||
|
refreshExpireTime = expireTime
|
||||||
|
}
|
||||||
|
|
||||||
user = refineUser(user)
|
user = refineUser(user)
|
||||||
|
|
||||||
_, originBackend := getOriginFromHost(host)
|
_, originBackend := getOriginFromHost(host)
|
||||||
|
|
||||||
name := util.GenerateId()
|
name := util.GenerateId()
|
||||||
jti := fmt.Sprintf("%s/%s", application.Owner, name)
|
jti := util.GetId(application.Owner, name)
|
||||||
|
|
||||||
claims := Claims{
|
claims := Claims{
|
||||||
User: user,
|
User: user,
|
||||||
|
@ -472,6 +472,13 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) bool {
|
|||||||
"location", "address", "country_code", "region", "language", "affiliation", "title", "homepage", "bio", "tag", "language", "gender", "birthday", "education", "score", "karma", "ranking", "signup_application",
|
"location", "address", "country_code", "region", "language", "affiliation", "title", "homepage", "bio", "tag", "language", "gender", "birthday", "education", "score", "karma", "ranking", "signup_application",
|
||||||
"is_admin", "is_global_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts",
|
"is_admin", "is_global_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts",
|
||||||
"signin_wrong_times", "last_signin_wrong_time",
|
"signin_wrong_times", "last_signin_wrong_time",
|
||||||
|
"github", "google", "qq", "wechat", "facebook", "dingtalk", "weibo", "gitee", "linkedin", "wecom", "lark", "gitlab", "adfs",
|
||||||
|
"baidu", "alipay", "casdoor", "infoflow", "apple", "azuread", "slack", "steam", "bilibili", "okta", "douyin", "line", "amazon",
|
||||||
|
"auth0", "battlenet", "bitbucket", "box", "cloudfoundry", "dailymotion", "deezer", "digitalocean", "discord", "dropbox",
|
||||||
|
"eveonline", "fitbit", "gitea", "heroku", "influxcloud", "instagram", "intercom", "kakao", "lastfm", "mailru", "meetup",
|
||||||
|
"microsoftonline", "naver", "nextcloud", "onedrive", "oura", "patreon", "paypal", "salesforce", "shopify", "soundcloud",
|
||||||
|
"spotify", "strava", "stripe", "tiktok", "tumblr", "twitch", "twitter", "typetalk", "uber", "vk", "wepay", "xero", "yahoo",
|
||||||
|
"yammer", "yandex", "zoom", "custom",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if isAdmin {
|
if isAdmin {
|
||||||
|
@ -15,6 +15,9 @@
|
|||||||
package object
|
package object
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"github.com/casdoor/casdoor/xlsx"
|
"github.com/casdoor/casdoor/xlsx"
|
||||||
)
|
)
|
||||||
@ -47,6 +50,26 @@ func parseLineItemBool(line *[]string, i int) bool {
|
|||||||
return parseLineItemInt(line, i) != 0
|
return parseLineItemInt(line, i) != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseListItem(lines *[]string, i int) []string {
|
||||||
|
if i >= len(*lines) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
line := (*lines)[i]
|
||||||
|
items := strings.Split(line, ";")
|
||||||
|
trimmedItems := make([]string, 0, len(items))
|
||||||
|
|
||||||
|
for _, item := range items {
|
||||||
|
trimmedItem := strings.TrimSpace(item)
|
||||||
|
if trimmedItem != "" {
|
||||||
|
trimmedItems = append(trimmedItems, trimmedItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(trimmedItems)
|
||||||
|
|
||||||
|
return trimmedItems
|
||||||
|
}
|
||||||
|
|
||||||
func UploadUsers(owner string, fileId string) bool {
|
func UploadUsers(owner string, fileId string) bool {
|
||||||
table := xlsx.ReadXlsxFile(fileId)
|
table := xlsx.ReadXlsxFile(fileId)
|
||||||
|
|
||||||
|
@ -33,10 +33,10 @@ type VerifyResult struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
VerificationSuccess int = 0
|
VerificationSuccess = iota
|
||||||
wrongCodeError = 1
|
wrongCodeError
|
||||||
noRecordError = 2
|
noRecordError
|
||||||
timeoutError = 3
|
timeoutError
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -94,3 +94,11 @@ func (pp *AlipayPaymentProvider) Notify(request *http.Request, body []byte, auth
|
|||||||
func (pp *AlipayPaymentProvider) GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error) {
|
func (pp *AlipayPaymentProvider) GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error) {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pp *AlipayPaymentProvider) GetResponseError(err error) string {
|
||||||
|
if err == nil {
|
||||||
|
return "success"
|
||||||
|
} else {
|
||||||
|
return "fail"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
8
pp/gc.go
8
pp/gc.go
@ -329,3 +329,11 @@ func (pp *GcPaymentProvider) GetInvoice(paymentName string, personName string, p
|
|||||||
|
|
||||||
return invoiceRespInfo.Url, nil
|
return invoiceRespInfo.Url, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pp *GcPaymentProvider) GetResponseError(err error) string {
|
||||||
|
if err == nil {
|
||||||
|
return "success"
|
||||||
|
} else {
|
||||||
|
return "fail"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -20,20 +20,20 @@ type PaymentProvider interface {
|
|||||||
Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error)
|
Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error)
|
||||||
Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error)
|
Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error)
|
||||||
GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error)
|
GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error)
|
||||||
|
GetResponseError(err error) string
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPaymentProvider(typ string, appId string, clientSecret string, host string, appCertificate string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string, clientId2 string) (PaymentProvider, error) {
|
func GetPaymentProvider(typ string, clientId string, clientSecret string, host string, appCertificate string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string, clientId2 string) (PaymentProvider, error) {
|
||||||
if typ == "Alipay" {
|
if typ == "Alipay" {
|
||||||
newAlipayPaymentProvider, err := NewAlipayPaymentProvider(appId, appCertificate, appPrivateKey, authorityPublicKey, authorityRootPublicKey)
|
newAlipayPaymentProvider, err := NewAlipayPaymentProvider(clientId, appCertificate, appPrivateKey, authorityPublicKey, authorityRootPublicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return newAlipayPaymentProvider, nil
|
return newAlipayPaymentProvider, nil
|
||||||
} else if typ == "GC" {
|
} else if typ == "GC" {
|
||||||
return NewGcPaymentProvider(appId, clientSecret, host), nil
|
return NewGcPaymentProvider(clientId, clientSecret, host), nil
|
||||||
} else if typ == "WeChat Pay" {
|
} else if typ == "WeChat Pay" {
|
||||||
// appId, mchId, mchCertSerialNumber, apiV3Key, privateKey
|
newWechatPaymentProvider, err := NewWechatPaymentProvider(clientId, clientSecret, clientId2, appCertificate, appPrivateKey)
|
||||||
newWechatPaymentProvider, err := NewWechatPaymentProvider(clientId2, appId, appCertificate, clientSecret, appPrivateKey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
12
pp/util.go
12
pp/util.go
@ -23,3 +23,15 @@ func getPriceString(price float64) string {
|
|||||||
priceString := strings.TrimRight(strings.TrimRight(fmt.Sprintf("%.2f", price), "0"), ".")
|
priceString := strings.TrimRight(strings.TrimRight(fmt.Sprintf("%.2f", price), "0"), ".")
|
||||||
return priceString
|
return priceString
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func joinAttachString(tokens []string) string {
|
||||||
|
return strings.Join(tokens, "|")
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseAttachString(s string) (string, string, string, error) {
|
||||||
|
tokens := strings.Split(s, "|")
|
||||||
|
if len(tokens) != 3 {
|
||||||
|
return "", "", "", fmt.Errorf("parseAttachString() error: len(tokens) expected 3, got: %d", len(tokens))
|
||||||
|
}
|
||||||
|
return tokens[0], tokens[1], tokens[2], nil
|
||||||
|
}
|
||||||
|
@ -16,7 +16,7 @@ package pp
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
@ -24,12 +24,21 @@ import (
|
|||||||
"github.com/go-pay/gopay/wechat/v3"
|
"github.com/go-pay/gopay/wechat/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type WechatPayNotifyResponse struct {
|
||||||
|
Code string `json:"Code"`
|
||||||
|
Message string `json:"Message"`
|
||||||
|
}
|
||||||
|
|
||||||
type WechatPaymentProvider struct {
|
type WechatPaymentProvider struct {
|
||||||
ClientV3 *wechat.ClientV3
|
ClientV3 *wechat.ClientV3
|
||||||
appId string
|
appId string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWechatPaymentProvider(appId string, mchId string, mchCertSerialNumber string, apiV3Key string, privateKey string) (*WechatPaymentProvider, error) {
|
func NewWechatPaymentProvider(mchId string, apiV3Key string, appId string, mchCertSerialNumber string, privateKey string) (*WechatPaymentProvider, error) {
|
||||||
|
if appId == "" && mchId == "" && mchCertSerialNumber == "" && apiV3Key == "" && privateKey == "" {
|
||||||
|
return &WechatPaymentProvider{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
pp := &WechatPaymentProvider{appId: appId}
|
pp := &WechatPaymentProvider{appId: appId}
|
||||||
|
|
||||||
clientV3, err := wechat.NewClientV3(mchId, mchCertSerialNumber, apiV3Key, privateKey)
|
clientV3, err := wechat.NewClientV3(mchId, mchCertSerialNumber, apiV3Key, privateKey)
|
||||||
@ -37,11 +46,13 @@ func NewWechatPaymentProvider(appId string, mchId string, mchCertSerialNumber st
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = clientV3.AutoVerifySign()
|
platformCert, serialNo, err := clientV3.GetAndSelectNewestCert()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
pp.ClientV3 = clientV3
|
|
||||||
|
pp.ClientV3 = clientV3.SetPlatformCert([]byte(platformCert), serialNo)
|
||||||
|
|
||||||
return pp, nil
|
return pp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,53 +61,71 @@ func (pp *WechatPaymentProvider) Pay(providerName string, productName string, pa
|
|||||||
|
|
||||||
bm := gopay.BodyMap{}
|
bm := gopay.BodyMap{}
|
||||||
|
|
||||||
bm.Set("providerName", providerName)
|
bm.Set("attach", joinAttachString([]string{productDisplayName, productName, providerName}))
|
||||||
bm.Set("productName", productName)
|
bm.Set("appid", pp.appId)
|
||||||
|
bm.Set("description", productDisplayName)
|
||||||
bm.Set("return_url", returnUrl)
|
|
||||||
bm.Set("notify_url", notifyUrl)
|
bm.Set("notify_url", notifyUrl)
|
||||||
|
|
||||||
bm.Set("body", productDisplayName)
|
|
||||||
bm.Set("out_trade_no", paymentName)
|
bm.Set("out_trade_no", paymentName)
|
||||||
bm.Set("total_fee", getPriceString(price))
|
bm.SetBodyMap("amount", func(bm gopay.BodyMap) {
|
||||||
|
bm.Set("total", int(price*100))
|
||||||
|
bm.Set("currency", "CNY")
|
||||||
|
})
|
||||||
|
|
||||||
wechatRsp, err := pp.ClientV3.V3TransactionJsapi(context.Background(), bm)
|
wxRsp, err := pp.ClientV3.V3TransactionNative(context.Background(), bm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
payUrl := fmt.Sprintf("https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect", pp.appId, wechatRsp.Response.PrepayId)
|
if wxRsp.Code != wechat.Success {
|
||||||
return payUrl, nil
|
return "", errors.New(wxRsp.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return wxRsp.Response.CodeUrl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *WechatPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error) {
|
func (pp *WechatPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error) {
|
||||||
bm, err := wechat.V3ParseNotifyToBodyMap(request)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", 0, "", "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
providerName := bm.Get("providerName")
|
|
||||||
productName := bm.Get("productName")
|
|
||||||
|
|
||||||
productDisplayName := bm.Get("body")
|
|
||||||
paymentName := bm.Get("out_trade_no")
|
|
||||||
price := util.ParseFloat(bm.Get("total_fee"))
|
|
||||||
|
|
||||||
notifyReq, err := wechat.V3ParseNotify(request)
|
notifyReq, err := wechat.V3ParseNotify(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cert := pp.ClientV3.WxPublicKey()
|
cert := pp.ClientV3.WxPublicKey()
|
||||||
|
|
||||||
err = notifyReq.VerifySignByPK(cert)
|
err = notifyReq.VerifySignByPK(cert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", 0, "", "", err
|
return "", "", 0, "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
apiKey := string(pp.ClientV3.ApiV3Key)
|
||||||
|
result, err := notifyReq.DecryptCipherText(apiKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", 0, "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
paymentName := result.OutTradeNo
|
||||||
|
price := float64(result.Amount.PayerTotal) / 100
|
||||||
|
|
||||||
|
productDisplayName, productName, providerName, err := parseAttachString(result.Attach)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", 0, "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
return productDisplayName, paymentName, price, productName, providerName, nil
|
return productDisplayName, paymentName, price, productName, providerName, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *WechatPaymentProvider) GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error) {
|
func (pp *WechatPaymentProvider) GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error) {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pp *WechatPaymentProvider) GetResponseError(err error) string {
|
||||||
|
response := &WechatPayNotifyResponse{
|
||||||
|
Code: "SUCCESS",
|
||||||
|
Message: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
response.Code = "FAIL"
|
||||||
|
response.Message = err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
return util.StructToJson(response)
|
||||||
|
}
|
||||||
|
@ -125,7 +125,11 @@ func AuthzFilter(ctx *context.Context) {
|
|||||||
subOwner, subName := getSubject(ctx)
|
subOwner, subName := getSubject(ctx)
|
||||||
method := ctx.Request.Method
|
method := ctx.Request.Method
|
||||||
urlPath := getUrlPath(ctx.Request.URL.Path)
|
urlPath := getUrlPath(ctx.Request.URL.Path)
|
||||||
objOwner, objName := getObject(ctx)
|
|
||||||
|
objOwner, objName := "", ""
|
||||||
|
if urlPath != "/api/get-app-login" {
|
||||||
|
objOwner, objName = getObject(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(urlPath, "/api/notify-payment") {
|
if strings.HasPrefix(urlPath, "/api/notify-payment") {
|
||||||
urlPath = "/api/notify-payment"
|
urlPath = "/api/notify-payment"
|
||||||
|
@ -43,7 +43,7 @@ func AutoSigninFilter(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
userId := fmt.Sprintf("%s/%s", token.Organization, token.User)
|
userId := util.GetId(token.Organization, token.User)
|
||||||
application, _ := object.GetApplicationByUserId(fmt.Sprintf("app/%s", token.Application))
|
application, _ := object.GetApplicationByUserId(fmt.Sprintf("app/%s", token.Application))
|
||||||
setSessionUser(ctx, userId)
|
setSessionUser(ctx, userId)
|
||||||
setSessionOidc(ctx, token.Scope, application.ClientId)
|
setSessionOidc(ctx, token.Scope, application.ClientId)
|
||||||
|
@ -15,8 +15,6 @@
|
|||||||
package routers
|
package routers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/beego/beego/context"
|
"github.com/beego/beego/context"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
@ -50,7 +48,7 @@ func getUserByClientIdSecret(ctx *context.Context) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("%s/%s", application.Organization, application.Name)
|
return util.GetId(application.Organization, application.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func RecordMessage(ctx *context.Context) {
|
func RecordMessage(ctx *context.Context) {
|
||||||
|
@ -65,6 +65,7 @@ func initAPI() {
|
|||||||
beego.Router("/api/add-organization", &controllers.ApiController{}, "POST:AddOrganization")
|
beego.Router("/api/add-organization", &controllers.ApiController{}, "POST:AddOrganization")
|
||||||
beego.Router("/api/delete-organization", &controllers.ApiController{}, "POST:DeleteOrganization")
|
beego.Router("/api/delete-organization", &controllers.ApiController{}, "POST:DeleteOrganization")
|
||||||
beego.Router("/api/get-default-application", &controllers.ApiController{}, "GET:GetDefaultApplication")
|
beego.Router("/api/get-default-application", &controllers.ApiController{}, "GET:GetDefaultApplication")
|
||||||
|
beego.Router("/api/get-organization-names", &controllers.ApiController{}, "GET:GetOrganizationNames")
|
||||||
|
|
||||||
beego.Router("/api/get-global-users", &controllers.ApiController{}, "GET:GetGlobalUsers")
|
beego.Router("/api/get-global-users", &controllers.ApiController{}, "GET:GetGlobalUsers")
|
||||||
beego.Router("/api/get-users", &controllers.ApiController{}, "GET:GetUsers")
|
beego.Router("/api/get-users", &controllers.ApiController{}, "GET:GetUsers")
|
||||||
@ -81,6 +82,7 @@ func initAPI() {
|
|||||||
beego.Router("/api/update-role", &controllers.ApiController{}, "POST:UpdateRole")
|
beego.Router("/api/update-role", &controllers.ApiController{}, "POST:UpdateRole")
|
||||||
beego.Router("/api/add-role", &controllers.ApiController{}, "POST:AddRole")
|
beego.Router("/api/add-role", &controllers.ApiController{}, "POST:AddRole")
|
||||||
beego.Router("/api/delete-role", &controllers.ApiController{}, "POST:DeleteRole")
|
beego.Router("/api/delete-role", &controllers.ApiController{}, "POST:DeleteRole")
|
||||||
|
beego.Router("/api/upload-roles", &controllers.ApiController{}, "POST:UploadRoles")
|
||||||
|
|
||||||
beego.Router("/api/get-permissions", &controllers.ApiController{}, "GET:GetPermissions")
|
beego.Router("/api/get-permissions", &controllers.ApiController{}, "GET:GetPermissions")
|
||||||
beego.Router("/api/get-permissions-by-submitter", &controllers.ApiController{}, "GET:GetPermissionsBySubmitter")
|
beego.Router("/api/get-permissions-by-submitter", &controllers.ApiController{}, "GET:GetPermissionsBySubmitter")
|
||||||
@ -89,6 +91,7 @@ func initAPI() {
|
|||||||
beego.Router("/api/update-permission", &controllers.ApiController{}, "POST:UpdatePermission")
|
beego.Router("/api/update-permission", &controllers.ApiController{}, "POST:UpdatePermission")
|
||||||
beego.Router("/api/add-permission", &controllers.ApiController{}, "POST:AddPermission")
|
beego.Router("/api/add-permission", &controllers.ApiController{}, "POST:AddPermission")
|
||||||
beego.Router("/api/delete-permission", &controllers.ApiController{}, "POST:DeletePermission")
|
beego.Router("/api/delete-permission", &controllers.ApiController{}, "POST:DeletePermission")
|
||||||
|
beego.Router("/api/upload-permissions", &controllers.ApiController{}, "POST:UploadPermissions")
|
||||||
|
|
||||||
beego.Router("/api/enforce", &controllers.ApiController{}, "POST:Enforce")
|
beego.Router("/api/enforce", &controllers.ApiController{}, "POST:Enforce")
|
||||||
beego.Router("/api/batch-enforce", &controllers.ApiController{}, "POST:BatchEnforce")
|
beego.Router("/api/batch-enforce", &controllers.ApiController{}, "POST:BatchEnforce")
|
||||||
@ -203,6 +206,24 @@ func initAPI() {
|
|||||||
beego.Router("/api/add-message", &controllers.ApiController{}, "POST:AddMessage")
|
beego.Router("/api/add-message", &controllers.ApiController{}, "POST:AddMessage")
|
||||||
beego.Router("/api/delete-message", &controllers.ApiController{}, "POST:DeleteMessage")
|
beego.Router("/api/delete-message", &controllers.ApiController{}, "POST:DeleteMessage")
|
||||||
|
|
||||||
|
beego.Router("/api/get-subscriptions", &controllers.ApiController{}, "GET:GetSubscriptions")
|
||||||
|
beego.Router("/api/get-subscription", &controllers.ApiController{}, "GET:GetSubscription")
|
||||||
|
beego.Router("/api/update-subscription", &controllers.ApiController{}, "POST:UpdateSubscription")
|
||||||
|
beego.Router("/api/add-subscription", &controllers.ApiController{}, "POST:AddSubscription")
|
||||||
|
beego.Router("/api/delete-subscription", &controllers.ApiController{}, "POST:DeleteSubscription")
|
||||||
|
|
||||||
|
beego.Router("/api/get-plans", &controllers.ApiController{}, "GET:GetPlans")
|
||||||
|
beego.Router("/api/get-plan", &controllers.ApiController{}, "GET:GetPlan")
|
||||||
|
beego.Router("/api/update-plan", &controllers.ApiController{}, "POST:UpdatePlan")
|
||||||
|
beego.Router("/api/add-plan", &controllers.ApiController{}, "POST:AddPlan")
|
||||||
|
beego.Router("/api/delete-plan", &controllers.ApiController{}, "POST:DeletePlan")
|
||||||
|
|
||||||
|
beego.Router("/api/get-pricings", &controllers.ApiController{}, "GET:GetPricings")
|
||||||
|
beego.Router("/api/get-pricing", &controllers.ApiController{}, "GET:GetPricing")
|
||||||
|
beego.Router("/api/update-pricing", &controllers.ApiController{}, "POST:UpdatePricing")
|
||||||
|
beego.Router("/api/add-pricing", &controllers.ApiController{}, "POST:AddPricing")
|
||||||
|
beego.Router("/api/delete-pricing", &controllers.ApiController{}, "POST:DeletePricing")
|
||||||
|
|
||||||
beego.Router("/api/get-products", &controllers.ApiController{}, "GET:GetProducts")
|
beego.Router("/api/get-products", &controllers.ApiController{}, "GET:GetProducts")
|
||||||
beego.Router("/api/get-product", &controllers.ApiController{}, "GET:GetProduct")
|
beego.Router("/api/get-product", &controllers.ApiController{}, "GET:GetProduct")
|
||||||
beego.Router("/api/update-product", &controllers.ApiController{}, "POST:UpdateProduct")
|
beego.Router("/api/update-product", &controllers.ApiController{}, "POST:UpdateProduct")
|
||||||
@ -247,6 +268,7 @@ func initAPI() {
|
|||||||
|
|
||||||
beego.Router("/api/get-system-info", &controllers.ApiController{}, "GET:GetSystemInfo")
|
beego.Router("/api/get-system-info", &controllers.ApiController{}, "GET:GetSystemInfo")
|
||||||
beego.Router("/api/get-version-info", &controllers.ApiController{}, "GET:GetVersionInfo")
|
beego.Router("/api/get-version-info", &controllers.ApiController{}, "GET:GetVersionInfo")
|
||||||
|
beego.Router("/api/health", &controllers.ApiController{}, "GET:Health")
|
||||||
beego.Router("/api/get-prometheus-info", &controllers.ApiController{}, "GET:GetPrometheusInfo")
|
beego.Router("/api/get-prometheus-info", &controllers.ApiController{}, "GET:GetPrometheusInfo")
|
||||||
|
|
||||||
beego.Handler("/api/metrics", promhttp.Handler())
|
beego.Handler("/api/metrics", promhttp.Handler())
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
package routers
|
package routers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"compress/gzip"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
@ -28,6 +30,7 @@ import (
|
|||||||
var (
|
var (
|
||||||
oldStaticBaseUrl = "https://cdn.casbin.org"
|
oldStaticBaseUrl = "https://cdn.casbin.org"
|
||||||
newStaticBaseUrl = conf.GetConfigString("staticBaseUrl")
|
newStaticBaseUrl = conf.GetConfigString("staticBaseUrl")
|
||||||
|
enableGzip, _ = conf.GetConfigBool("enableGzip")
|
||||||
)
|
)
|
||||||
|
|
||||||
func StaticFilter(ctx *context.Context) {
|
func StaticFilter(ctx *context.Context) {
|
||||||
@ -53,7 +56,7 @@ func StaticFilter(ctx *context.Context) {
|
|||||||
|
|
||||||
path2 := strings.TrimLeft(path, "web/build/images/")
|
path2 := strings.TrimLeft(path, "web/build/images/")
|
||||||
if util.FileExist(path2) {
|
if util.FileExist(path2) {
|
||||||
http.ServeFile(ctx.ResponseWriter, ctx.Request, path2)
|
makeGzipResponse(ctx.ResponseWriter, ctx.Request, path2)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,7 +65,7 @@ func StaticFilter(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if oldStaticBaseUrl == newStaticBaseUrl {
|
if oldStaticBaseUrl == newStaticBaseUrl {
|
||||||
http.ServeFile(ctx.ResponseWriter, ctx.Request, path)
|
makeGzipResponse(ctx.ResponseWriter, ctx.Request, path)
|
||||||
} else {
|
} else {
|
||||||
serveFileWithReplace(ctx.ResponseWriter, ctx.Request, path, oldStaticBaseUrl, newStaticBaseUrl)
|
serveFileWithReplace(ctx.ResponseWriter, ctx.Request, path, oldStaticBaseUrl, newStaticBaseUrl)
|
||||||
}
|
}
|
||||||
@ -89,3 +92,24 @@ func serveFileWithReplace(w http.ResponseWriter, r *http.Request, name string, o
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type gzipResponseWriter struct {
|
||||||
|
io.Writer
|
||||||
|
http.ResponseWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w gzipResponseWriter) Write(b []byte) (int, error) {
|
||||||
|
return w.Writer.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeGzipResponse(w http.ResponseWriter, r *http.Request, path string) {
|
||||||
|
if !enableGzip || !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
|
||||||
|
http.ServeFile(w, r, path)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Encoding", "gzip")
|
||||||
|
gz := gzip.NewWriter(w)
|
||||||
|
defer gz.Close()
|
||||||
|
gzw := gzipResponseWriter{Writer: gz, ResponseWriter: w}
|
||||||
|
http.ServeFile(gzw, r, path)
|
||||||
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -177,6 +177,42 @@ paths:
|
|||||||
description: The Response object
|
description: The Response object
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/controllers.Response'
|
$ref: '#/definitions/controllers.Response'
|
||||||
|
/api/add-plan:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- Plan API
|
||||||
|
description: add plan
|
||||||
|
operationId: ApiController.AddPlan
|
||||||
|
parameters:
|
||||||
|
- in: body
|
||||||
|
name: body
|
||||||
|
description: The details of the plan
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/object.Plan'
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controllers.Response'
|
||||||
|
/api/add-pricing:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- Pricing API
|
||||||
|
description: add pricing
|
||||||
|
operationId: ApiController.AddPricing
|
||||||
|
parameters:
|
||||||
|
- in: body
|
||||||
|
name: body
|
||||||
|
description: The details of the pricing
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/object.Pricing'
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controllers.Response'
|
||||||
/api/add-product:
|
/api/add-product:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@ -278,6 +314,24 @@ paths:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
|
/api/add-subscription:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- Subscription API
|
||||||
|
description: add subscription
|
||||||
|
operationId: ApiController.AddSubscription
|
||||||
|
parameters:
|
||||||
|
- in: body
|
||||||
|
name: body
|
||||||
|
description: The details of the subscription
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/object.Subscription'
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controllers.Response'
|
||||||
/api/add-syncer:
|
/api/add-syncer:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@ -552,6 +606,17 @@ paths:
|
|||||||
description: The Response object
|
description: The Response object
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/controllers.Response'
|
$ref: '#/definitions/controllers.Response'
|
||||||
|
/api/delete-mfa/:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- MFA API
|
||||||
|
description: ': Delete MFA'
|
||||||
|
operationId: ApiController.DeleteMfa
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Response'
|
||||||
/api/delete-model:
|
/api/delete-model:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@ -624,6 +689,42 @@ paths:
|
|||||||
description: The Response object
|
description: The Response object
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/controllers.Response'
|
$ref: '#/definitions/controllers.Response'
|
||||||
|
/api/delete-plan:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- Plan API
|
||||||
|
description: delete plan
|
||||||
|
operationId: ApiController.DeletePlan
|
||||||
|
parameters:
|
||||||
|
- in: body
|
||||||
|
name: body
|
||||||
|
description: The details of the plan
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/object.Plan'
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controllers.Response'
|
||||||
|
/api/delete-pricing:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- Pricing API
|
||||||
|
description: delete pricing
|
||||||
|
operationId: ApiController.DeletePricing
|
||||||
|
parameters:
|
||||||
|
- in: body
|
||||||
|
name: body
|
||||||
|
description: The details of the pricing
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/object.Pricing'
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controllers.Response'
|
||||||
/api/delete-product:
|
/api/delete-product:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@ -702,6 +803,24 @@ paths:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
|
/api/delete-subscription:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- Subscription API
|
||||||
|
description: delete subscription
|
||||||
|
operationId: ApiController.DeleteSubscription
|
||||||
|
parameters:
|
||||||
|
- in: body
|
||||||
|
name: body
|
||||||
|
description: The details of the subscription
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/object.Subscription'
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controllers.Response'
|
||||||
/api/delete-syncer:
|
/api/delete-syncer:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@ -995,6 +1114,19 @@ paths:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/object.User'
|
$ref: '#/definitions/object.User'
|
||||||
|
/api/get-globle-certs:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Cert API
|
||||||
|
description: get globle certs
|
||||||
|
operationId: ApiController.GetGlobleCerts
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/object.Cert'
|
||||||
/api/get-ldap:
|
/api/get-ldap:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@ -1027,6 +1159,23 @@ paths:
|
|||||||
description: The Response object
|
description: The Response object
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/object.Message'
|
$ref: '#/definitions/object.Message'
|
||||||
|
/api/get-message-answer:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Message API
|
||||||
|
description: get message answer
|
||||||
|
operationId: ApiController.GetMessageAnswer
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: id
|
||||||
|
description: The id ( owner/name ) of the message
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/object.Message'
|
||||||
/api/get-messages:
|
/api/get-messages:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@ -1241,6 +1390,82 @@ paths:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/object.Permission'
|
$ref: '#/definitions/object.Permission'
|
||||||
|
/api/get-plan:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Plan API
|
||||||
|
description: get plan
|
||||||
|
operationId: ApiController.GetPlan
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: id
|
||||||
|
description: The id ( owner/name ) of the plan
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: includeOption
|
||||||
|
description: Should include plan's option
|
||||||
|
type: boolean
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/object.Plan'
|
||||||
|
/api/get-plans:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Plan API
|
||||||
|
description: get plans
|
||||||
|
operationId: ApiController.GetPlans
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: owner
|
||||||
|
description: The owner of plans
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/object.Plan'
|
||||||
|
/api/get-pricing:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Pricing API
|
||||||
|
description: get pricing
|
||||||
|
operationId: ApiController.GetPricing
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: id
|
||||||
|
description: The id ( owner/name ) of the pricing
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/object.pricing'
|
||||||
|
/api/get-pricings:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Pricing API
|
||||||
|
description: get pricings
|
||||||
|
operationId: ApiController.GetPricings
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: owner
|
||||||
|
description: The owner of pricings
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/object.Pricing'
|
||||||
/api/get-product:
|
/api/get-product:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@ -1277,6 +1502,17 @@ paths:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/object.Product'
|
$ref: '#/definitions/object.Product'
|
||||||
|
/api/get-prometheus-info:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Prometheus API
|
||||||
|
description: get Prometheus Info
|
||||||
|
operationId: ApiController.GetPrometheusInfo
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/object.PrometheusInfo'
|
||||||
/api/get-provider:
|
/api/get-provider:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@ -1466,6 +1702,42 @@ paths:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/object.User'
|
$ref: '#/definitions/object.User'
|
||||||
|
/api/get-subscription:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Subscription API
|
||||||
|
description: get subscription
|
||||||
|
operationId: ApiController.GetSubscription
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: id
|
||||||
|
description: The id ( owner/name ) of the subscription
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/object.subscription'
|
||||||
|
/api/get-subscriptions:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Subscription API
|
||||||
|
description: get subscriptions
|
||||||
|
operationId: ApiController.GetSubscriptions
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: owner
|
||||||
|
description: The owner of subscriptions
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/object.Subscription'
|
||||||
/api/get-syncer:
|
/api/get-syncer:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@ -1975,6 +2247,39 @@ paths:
|
|||||||
description: The Response object
|
description: The Response object
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/controllers.Response'
|
$ref: '#/definitions/controllers.Response'
|
||||||
|
/api/mfa/setup/enable:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- MFA API
|
||||||
|
description: enable totp
|
||||||
|
operationId: ApiController.MfaSetupEnable
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Response'
|
||||||
|
/api/mfa/setup/initiate:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- MFA API
|
||||||
|
description: setup MFA
|
||||||
|
operationId: ApiController.MfaSetupInitiate
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Response object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/The'
|
||||||
|
/api/mfa/setup/verify:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- MFA API
|
||||||
|
description: setup verify totp
|
||||||
|
operationId: ApiController.MfaSetupVerify
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Response'
|
||||||
/api/notify-payment:
|
/api/notify-payment:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@ -2048,6 +2353,17 @@ paths:
|
|||||||
description: The Response object
|
description: The Response object
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/controllers.Response'
|
$ref: '#/definitions/controllers.Response'
|
||||||
|
/api/set-preferred-mfa:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- MFA API
|
||||||
|
description: ': Set specific Mfa Preferred'
|
||||||
|
operationId: ApiController.SetPreferredMfa
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Response'
|
||||||
/api/signup:
|
/api/signup:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@ -2268,6 +2584,52 @@ paths:
|
|||||||
description: The Response object
|
description: The Response object
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/controllers.Response'
|
$ref: '#/definitions/controllers.Response'
|
||||||
|
/api/update-plan:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- Plan API
|
||||||
|
description: update plan
|
||||||
|
operationId: ApiController.UpdatePlan
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: id
|
||||||
|
description: The id ( owner/name ) of the plan
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- in: body
|
||||||
|
name: body
|
||||||
|
description: The details of the plan
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/object.Plan'
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controllers.Response'
|
||||||
|
/api/update-pricing:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- Pricing API
|
||||||
|
description: update pricing
|
||||||
|
operationId: ApiController.UpdatePricing
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: id
|
||||||
|
description: The id ( owner/name ) of the pricing
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- in: body
|
||||||
|
name: body
|
||||||
|
description: The details of the pricing
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/object.Pricing'
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controllers.Response'
|
||||||
/api/update-product:
|
/api/update-product:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@ -2361,6 +2723,29 @@ paths:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
|
/api/update-subscription:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- Subscription API
|
||||||
|
description: update subscription
|
||||||
|
operationId: ApiController.UpdateSubscription
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: id
|
||||||
|
description: The id ( owner/name ) of the subscription
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- in: body
|
||||||
|
name: body
|
||||||
|
description: The details of the subscription
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/object.Subscription'
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controllers.Response'
|
||||||
/api/update-syncer:
|
/api/update-syncer:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@ -2555,10 +2940,10 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Response'
|
$ref: '#/definitions/Response'
|
||||||
definitions:
|
definitions:
|
||||||
1183.0xc000455050.false:
|
1183.0x1400042eb70.false:
|
||||||
title: "false"
|
title: "false"
|
||||||
type: object
|
type: object
|
||||||
1217.0xc000455080.false:
|
1217.0x1400042eba0.false:
|
||||||
title: "false"
|
title: "false"
|
||||||
type: object
|
type: object
|
||||||
LaravelResponse:
|
LaravelResponse:
|
||||||
@ -2567,6 +2952,9 @@ definitions:
|
|||||||
Response:
|
Response:
|
||||||
title: Response
|
title: Response
|
||||||
type: object
|
type: object
|
||||||
|
The:
|
||||||
|
title: The
|
||||||
|
type: object
|
||||||
controllers.AuthForm:
|
controllers.AuthForm:
|
||||||
title: AuthForm
|
title: AuthForm
|
||||||
type: object
|
type: object
|
||||||
@ -2591,9 +2979,9 @@ definitions:
|
|||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
data:
|
data:
|
||||||
$ref: '#/definitions/1183.0xc000455050.false'
|
$ref: '#/definitions/1183.0x1400042eb70.false'
|
||||||
data2:
|
data2:
|
||||||
$ref: '#/definitions/1217.0xc000455080.false'
|
$ref: '#/definitions/1217.0x1400042eba0.false'
|
||||||
msg:
|
msg:
|
||||||
type: string
|
type: string
|
||||||
name:
|
name:
|
||||||
@ -2799,6 +3187,17 @@ definitions:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
|
object.GaugeVecInfo:
|
||||||
|
title: GaugeVecInfo
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
method:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
throughput:
|
||||||
|
type: number
|
||||||
|
format: double
|
||||||
object.Header:
|
object.Header:
|
||||||
title: Header
|
title: Header
|
||||||
type: object
|
type: object
|
||||||
@ -2807,6 +3206,19 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
value:
|
value:
|
||||||
type: string
|
type: string
|
||||||
|
object.HistogramVecInfo:
|
||||||
|
title: HistogramVecInfo
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
count:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
latency:
|
||||||
|
type: string
|
||||||
|
method:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
object.IntrospectionResponse:
|
object.IntrospectionResponse:
|
||||||
title: IntrospectionResponse
|
title: IntrospectionResponse
|
||||||
type: object
|
type: object
|
||||||
@ -2868,8 +3280,30 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
owner:
|
owner:
|
||||||
type: string
|
type: string
|
||||||
|
replyTo:
|
||||||
|
type: string
|
||||||
text:
|
text:
|
||||||
type: string
|
type: string
|
||||||
|
object.MfaProps:
|
||||||
|
title: MfaProps
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
countryCode:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
isPreferred:
|
||||||
|
type: boolean
|
||||||
|
recoveryCodes:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
secret:
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
url:
|
||||||
|
type: string
|
||||||
object.Model:
|
object.Model:
|
||||||
title: Model
|
title: Model
|
||||||
type: object
|
type: object
|
||||||
@ -3098,6 +3532,67 @@ definitions:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
|
object.Plan:
|
||||||
|
title: Plan
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
createdTime:
|
||||||
|
type: string
|
||||||
|
currency:
|
||||||
|
type: string
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
displayName:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
owner:
|
||||||
|
type: string
|
||||||
|
pricePerMonth:
|
||||||
|
type: number
|
||||||
|
format: double
|
||||||
|
pricePerYear:
|
||||||
|
type: number
|
||||||
|
format: double
|
||||||
|
role:
|
||||||
|
type: string
|
||||||
|
options:
|
||||||
|
type: array
|
||||||
|
object.Pricing:
|
||||||
|
title: Pricing
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
application:
|
||||||
|
type: string
|
||||||
|
approveTime:
|
||||||
|
type: string
|
||||||
|
approver:
|
||||||
|
type: string
|
||||||
|
createdTime:
|
||||||
|
type: string
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
displayName:
|
||||||
|
type: string
|
||||||
|
hasTrial:
|
||||||
|
type: boolean
|
||||||
|
isEnabled:
|
||||||
|
type: boolean
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
owner:
|
||||||
|
type: string
|
||||||
|
plans:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
state:
|
||||||
|
type: string
|
||||||
|
submitter:
|
||||||
|
type: string
|
||||||
|
trialDuration:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
object.Product:
|
object.Product:
|
||||||
title: Product
|
title: Product
|
||||||
type: object
|
type: object
|
||||||
@ -3141,6 +3636,21 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
tag:
|
tag:
|
||||||
type: string
|
type: string
|
||||||
|
object.PrometheusInfo:
|
||||||
|
title: PrometheusInfo
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
apiLatency:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/object.HistogramVecInfo'
|
||||||
|
apiThroughput:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/object.GaugeVecInfo'
|
||||||
|
totalThroughput:
|
||||||
|
type: number
|
||||||
|
format: double
|
||||||
object.Provider:
|
object.Provider:
|
||||||
title: Provider
|
title: Provider
|
||||||
type: object
|
type: object
|
||||||
@ -3313,6 +3823,43 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
visible:
|
visible:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
object.Subscription:
|
||||||
|
title: Subscription
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
approveTime:
|
||||||
|
type: string
|
||||||
|
approver:
|
||||||
|
type: string
|
||||||
|
createdTime:
|
||||||
|
type: string
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
displayName:
|
||||||
|
type: string
|
||||||
|
duration:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
endDate:
|
||||||
|
type: string
|
||||||
|
format: datetime
|
||||||
|
isEnabled:
|
||||||
|
type: boolean
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
owner:
|
||||||
|
type: string
|
||||||
|
plan:
|
||||||
|
type: string
|
||||||
|
startDate:
|
||||||
|
type: string
|
||||||
|
format: datetime
|
||||||
|
state:
|
||||||
|
type: string
|
||||||
|
submitter:
|
||||||
|
type: string
|
||||||
|
user:
|
||||||
|
type: string
|
||||||
object.Syncer:
|
object.Syncer:
|
||||||
title: Syncer
|
title: Syncer
|
||||||
type: object
|
type: object
|
||||||
@ -3612,6 +4159,10 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
microsoftonline:
|
microsoftonline:
|
||||||
type: string
|
type: string
|
||||||
|
multiFactorAuths:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/object.MfaProps'
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
naver:
|
naver:
|
||||||
@ -3778,6 +4329,12 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
url:
|
url:
|
||||||
type: string
|
type: string
|
||||||
|
object.pricing:
|
||||||
|
title: pricing
|
||||||
|
type: object
|
||||||
|
object.subscription:
|
||||||
|
title: subscription
|
||||||
|
type: object
|
||||||
protocol.CredentialAssertion:
|
protocol.CredentialAssertion:
|
||||||
title: CredentialAssertion
|
title: CredentialAssertion
|
||||||
type: object
|
type: object
|
||||||
|
@ -66,13 +66,13 @@ func (db *Database) OnRow(e *canal.RowsEvent) error {
|
|||||||
for i, row := range e.Rows {
|
for i, row := range e.Rows {
|
||||||
for j, item := range row {
|
for j, item := range row {
|
||||||
if i%2 == 0 {
|
if i%2 == 0 {
|
||||||
if isChar[j] == true {
|
if isChar[j] {
|
||||||
oldColumnValue[j] = fmt.Sprintf("%s", item)
|
oldColumnValue[j] = fmt.Sprintf("%s", item)
|
||||||
} else {
|
} else {
|
||||||
oldColumnValue[j] = fmt.Sprintf("%d", item)
|
oldColumnValue[j] = fmt.Sprintf("%d", item)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if isChar[j] == true {
|
if isChar[j] {
|
||||||
if item == nil {
|
if item == nil {
|
||||||
newColumnValue[j] = nil
|
newColumnValue[j] = nil
|
||||||
} else {
|
} else {
|
||||||
@ -103,7 +103,7 @@ func (db *Database) OnRow(e *canal.RowsEvent) error {
|
|||||||
db.engine.Exec("BEGIN")
|
db.engine.Exec("BEGIN")
|
||||||
for _, row := range e.Rows {
|
for _, row := range e.Rows {
|
||||||
for j, item := range row {
|
for j, item := range row {
|
||||||
if isChar[j] == true {
|
if isChar[j] {
|
||||||
oldColumnValue[j] = fmt.Sprintf("%s", item)
|
oldColumnValue[j] = fmt.Sprintf("%s", item)
|
||||||
} else {
|
} else {
|
||||||
oldColumnValue[j] = fmt.Sprintf("%d", item)
|
oldColumnValue[j] = fmt.Sprintf("%d", item)
|
||||||
@ -128,7 +128,7 @@ func (db *Database) OnRow(e *canal.RowsEvent) error {
|
|||||||
db.engine.Exec("BEGIN")
|
db.engine.Exec("BEGIN")
|
||||||
for _, row := range e.Rows {
|
for _, row := range e.Rows {
|
||||||
for j, item := range row {
|
for j, item := range row {
|
||||||
if isChar[j] == true {
|
if isChar[j] {
|
||||||
if item == nil {
|
if item == nil {
|
||||||
newColumnValue[j] = nil
|
newColumnValue[j] = nil
|
||||||
} else {
|
} else {
|
||||||
|
@ -25,7 +25,7 @@ import (
|
|||||||
var rePhone *regexp.Regexp
|
var rePhone *regexp.Regexp
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rePhone, _ = regexp.Compile("(\\d{3})\\d*(\\d{4})")
|
rePhone, _ = regexp.Compile(`(\d{3})\d*(\d{4})`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsEmailValid(email string) bool {
|
func IsEmailValid(email string) bool {
|
||||||
|
@ -44,6 +44,12 @@ import SyncerListPage from "./SyncerListPage";
|
|||||||
import SyncerEditPage from "./SyncerEditPage";
|
import SyncerEditPage from "./SyncerEditPage";
|
||||||
import CertListPage from "./CertListPage";
|
import CertListPage from "./CertListPage";
|
||||||
import CertEditPage from "./CertEditPage";
|
import CertEditPage from "./CertEditPage";
|
||||||
|
import SubscriptionListPage from "./SubscriptionListPage";
|
||||||
|
import SubscriptionEditPage from "./SubscriptionEditPage";
|
||||||
|
import PricingListPage from "./PricingListPage";
|
||||||
|
import PricingEditPage from "./PricingEditPage";
|
||||||
|
import PlanListPage from "./PlanListPage";
|
||||||
|
import PlanEditPage from "./PlanEditPage";
|
||||||
import ChatListPage from "./ChatListPage";
|
import ChatListPage from "./ChatListPage";
|
||||||
import ChatEditPage from "./ChatEditPage";
|
import ChatEditPage from "./ChatEditPage";
|
||||||
import ChatPage from "./ChatPage";
|
import ChatPage from "./ChatPage";
|
||||||
@ -168,6 +174,12 @@ class App extends Component {
|
|||||||
this.setState({selectedMenuKey: "/result"});
|
this.setState({selectedMenuKey: "/result"});
|
||||||
} else if (uri.includes("/sysinfo")) {
|
} else if (uri.includes("/sysinfo")) {
|
||||||
this.setState({selectedMenuKey: "/sysinfo"});
|
this.setState({selectedMenuKey: "/sysinfo"});
|
||||||
|
} else if (uri.includes("/subscriptions")) {
|
||||||
|
this.setState({selectedMenuKey: "/subscriptions"});
|
||||||
|
} else if (uri.includes("/plans")) {
|
||||||
|
this.setState({selectedMenuKey: "/plans"});
|
||||||
|
} else if (uri.includes("/pricings")) {
|
||||||
|
this.setState({selectedMenuKey: "/pricings"});
|
||||||
} else {
|
} else {
|
||||||
this.setState({selectedMenuKey: -1});
|
this.setState({selectedMenuKey: -1});
|
||||||
}
|
}
|
||||||
@ -335,6 +347,8 @@ class App extends Component {
|
|||||||
const onClick = (e) => {
|
const onClick = (e) => {
|
||||||
if (e.key === "/account") {
|
if (e.key === "/account") {
|
||||||
this.props.history.push("/account");
|
this.props.history.push("/account");
|
||||||
|
} else if (e.key === "/subscription") {
|
||||||
|
this.props.history.push("/subscription");
|
||||||
} else if (e.key === "/chat") {
|
} else if (e.key === "/chat") {
|
||||||
this.props.history.push("/chat");
|
this.props.history.push("/chat");
|
||||||
} else if (e.key === "/logout") {
|
} else if (e.key === "/logout") {
|
||||||
@ -444,6 +458,19 @@ class App extends Component {
|
|||||||
res.push(Setting.getItem(<Link to="/records">{i18next.t("general:Records")}</Link>,
|
res.push(Setting.getItem(<Link to="/records">{i18next.t("general:Records")}</Link>,
|
||||||
"/records"
|
"/records"
|
||||||
));
|
));
|
||||||
|
|
||||||
|
res.push(Setting.getItem(<Link to="/plans">{i18next.t("general:Plans")}</Link>,
|
||||||
|
"/plans"
|
||||||
|
));
|
||||||
|
|
||||||
|
res.push(Setting.getItem(<Link to="/pricings">{i18next.t("general:Pricings")}</Link>,
|
||||||
|
"/pricings"
|
||||||
|
));
|
||||||
|
|
||||||
|
res.push(Setting.getItem(<Link to="/subscriptions">{i18next.t("general:Subscriptions")}</Link>,
|
||||||
|
"/subscriptions"
|
||||||
|
));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Setting.isLocalAdminUser(this.state.account)) {
|
if (Setting.isLocalAdminUser(this.state.account)) {
|
||||||
@ -468,6 +495,7 @@ class App extends Component {
|
|||||||
));
|
));
|
||||||
|
|
||||||
if (Conf.EnableExtraPages) {
|
if (Conf.EnableExtraPages) {
|
||||||
|
|
||||||
res.push(Setting.getItem(<Link to="/products">{i18next.t("general:Products")}</Link>,
|
res.push(Setting.getItem(<Link to="/products">{i18next.t("general:Products")}</Link>,
|
||||||
"/products"
|
"/products"
|
||||||
));
|
));
|
||||||
@ -550,12 +578,18 @@ class App extends Component {
|
|||||||
<Route exact path="/syncers" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/syncers" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/syncers/:syncerName" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/syncers/:syncerName" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerEditPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/certs" render={(props) => this.renderLoginIfNotLoggedIn(<CertListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/certs" render={(props) => this.renderLoginIfNotLoggedIn(<CertListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/certs/:certName" render={(props) => this.renderLoginIfNotLoggedIn(<CertEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/certs/:organizationName/:certName" render={(props) => this.renderLoginIfNotLoggedIn(<CertEditPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/chats" render={(props) => this.renderLoginIfNotLoggedIn(<ChatListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/chats" render={(props) => this.renderLoginIfNotLoggedIn(<ChatListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/chats/:chatName" render={(props) => this.renderLoginIfNotLoggedIn(<ChatEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/chats/:chatName" render={(props) => this.renderLoginIfNotLoggedIn(<ChatEditPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/chat" render={(props) => this.renderLoginIfNotLoggedIn(<ChatPage account={this.state.account} {...props} />)} />
|
<Route exact path="/chat" render={(props) => this.renderLoginIfNotLoggedIn(<ChatPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/messages" render={(props) => this.renderLoginIfNotLoggedIn(<MessageListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/messages" render={(props) => this.renderLoginIfNotLoggedIn(<MessageListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/messages/:messageName" render={(props) => this.renderLoginIfNotLoggedIn(<MessageEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/messages/:messageName" render={(props) => this.renderLoginIfNotLoggedIn(<MessageEditPage account={this.state.account} {...props} />)} />
|
||||||
|
<Route exact path="/plans" render={(props) => this.renderLoginIfNotLoggedIn(<PlanListPage account={this.state.account} {...props} />)} />
|
||||||
|
<Route exact path="/plan/:organizationName/:planName" render={(props) => this.renderLoginIfNotLoggedIn(<PlanEditPage account={this.state.account} {...props} />)} />
|
||||||
|
<Route exact path="/pricings" render={(props) => this.renderLoginIfNotLoggedIn(<PricingListPage account={this.state.account} {...props} />)} />
|
||||||
|
<Route exact path="/pricing/:organizationName/:pricingName" render={(props) => this.renderLoginIfNotLoggedIn(<PricingEditPage account={this.state.account} {...props} />)} />
|
||||||
|
<Route exact path="/subscriptions" render={(props) => this.renderLoginIfNotLoggedIn(<SubscriptionListPage account={this.state.account} {...props} />)} />
|
||||||
|
<Route exact path="/subscription/:organizationName/:subscriptionName" render={(props) => this.renderLoginIfNotLoggedIn(<SubscriptionEditPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/products" render={(props) => this.renderLoginIfNotLoggedIn(<ProductListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/products" render={(props) => this.renderLoginIfNotLoggedIn(<ProductListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/products/:productName" render={(props) => this.renderLoginIfNotLoggedIn(<ProductEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/products/:productName" render={(props) => this.renderLoginIfNotLoggedIn(<ProductEditPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/products/:productName/buy" render={(props) => this.renderLoginIfNotLoggedIn(<ProductBuyPage account={this.state.account} {...props} />)} />
|
<Route exact path="/products/:productName/buy" render={(props) => this.renderLoginIfNotLoggedIn(<ProductBuyPage account={this.state.account} {...props} />)} />
|
||||||
@ -674,7 +708,8 @@ class App extends Component {
|
|||||||
window.location.pathname.startsWith("/prompt") ||
|
window.location.pathname.startsWith("/prompt") ||
|
||||||
window.location.pathname.startsWith("/result") ||
|
window.location.pathname.startsWith("/result") ||
|
||||||
window.location.pathname.startsWith("/cas") ||
|
window.location.pathname.startsWith("/cas") ||
|
||||||
window.location.pathname.startsWith("/auto-signup");
|
window.location.pathname.startsWith("/auto-signup") ||
|
||||||
|
window.location.pathname.startsWith("/select-plan");
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPage() {
|
renderPage() {
|
||||||
|
@ -112,7 +112,6 @@ class ApplicationEditPage extends React.Component {
|
|||||||
UNSAFE_componentWillMount() {
|
UNSAFE_componentWillMount() {
|
||||||
this.getApplication();
|
this.getApplication();
|
||||||
this.getOrganizations();
|
this.getOrganizations();
|
||||||
this.getCerts();
|
|
||||||
this.getProviders();
|
this.getProviders();
|
||||||
this.getSamlMetadata();
|
this.getSamlMetadata();
|
||||||
}
|
}
|
||||||
@ -126,6 +125,8 @@ class ApplicationEditPage extends React.Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
application: application,
|
application: application,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.getCerts(application.organization);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,8 +145,8 @@ class ApplicationEditPage extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getCerts() {
|
getCerts(owner) {
|
||||||
CertBackend.getCerts(this.props.account.owner)
|
CertBackend.getCerts(owner)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
certs: (res.msg === undefined) ? res : [],
|
certs: (res.msg === undefined) ? res : [],
|
||||||
@ -440,6 +441,26 @@ class ApplicationEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("application:Org choice mode"), i18next.t("application:Org choice mode - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: "100%"}}
|
||||||
|
options={[
|
||||||
|
{label: i18next.t("general:None"), value: "None"},
|
||||||
|
{label: i18next.t("application:Select"), value: "Select"},
|
||||||
|
{label: i18next.t("application:Input"), value: "Input"},
|
||||||
|
].map((item) => {
|
||||||
|
return Setting.getOption(item.label, item.value);
|
||||||
|
})}
|
||||||
|
value={this.state.application.orgChoiceMode ?? []}
|
||||||
|
onChange={(value => {
|
||||||
|
this.updateApplicationField("orgChoiceMode", value);
|
||||||
|
})} >
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("general:Signup URL"), i18next.t("general:Signup URL - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Signup URL"), i18next.t("general:Signup URL - Tooltip"))} :
|
||||||
@ -790,7 +811,7 @@ class ApplicationEditPage extends React.Component {
|
|||||||
let signUpUrl = `/signup/${this.state.application.name}`;
|
let signUpUrl = `/signup/${this.state.application.name}`;
|
||||||
|
|
||||||
let redirectUri;
|
let redirectUri;
|
||||||
if (this.state.application.redirectUris.length !== 0) {
|
if (this.state.application.redirectUris?.length > 0) {
|
||||||
redirectUri = this.state.application.redirectUris[0];
|
redirectUri = this.state.application.redirectUris[0];
|
||||||
} else {
|
} else {
|
||||||
redirectUri = "\"ERROR: You must specify at least one Redirect URL in 'Redirect URLs'\"";
|
redirectUri = "\"ERROR: You must specify at least one Redirect URL in 'Redirect URLs'\"";
|
||||||
|
@ -65,6 +65,7 @@ class ApplicationListPage extends BaseListPage {
|
|||||||
redirectUris: ["http://localhost:9000/callback"],
|
redirectUris: ["http://localhost:9000/callback"],
|
||||||
tokenFormat: "JWT",
|
tokenFormat: "JWT",
|
||||||
expireInHours: 24 * 7,
|
expireInHours: 24 * 7,
|
||||||
|
refreshExpireInHours: 24 * 7,
|
||||||
formOffset: 2,
|
formOffset: 2,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -175,7 +176,7 @@ class ApplicationListPage extends BaseListPage {
|
|||||||
// width: '600px',
|
// width: '600px',
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
const providers = text;
|
const providers = text;
|
||||||
if (providers.length === 0) {
|
if (providers === null || providers.length === 0) {
|
||||||
return `(${i18next.t("general:empty")})`;
|
return `(${i18next.t("general:empty")})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import {Button, Card, Col, Input, InputNumber, Row, Select} from "antd";
|
import {Button, Card, Col, Input, InputNumber, Row, Select} from "antd";
|
||||||
import * as CertBackend from "./backend/CertBackend";
|
import * as CertBackend from "./backend/CertBackend";
|
||||||
|
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import copy from "copy-to-clipboard";
|
import copy from "copy-to-clipboard";
|
||||||
@ -29,6 +30,7 @@ class CertEditPage extends React.Component {
|
|||||||
this.state = {
|
this.state = {
|
||||||
classes: props,
|
classes: props,
|
||||||
certName: props.match.params.certName,
|
certName: props.match.params.certName,
|
||||||
|
owner: props.match.params.organizationName,
|
||||||
cert: null,
|
cert: null,
|
||||||
organizations: [],
|
organizations: [],
|
||||||
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
||||||
@ -37,10 +39,11 @@ class CertEditPage extends React.Component {
|
|||||||
|
|
||||||
UNSAFE_componentWillMount() {
|
UNSAFE_componentWillMount() {
|
||||||
this.getCert();
|
this.getCert();
|
||||||
|
this.getOrganizations();
|
||||||
}
|
}
|
||||||
|
|
||||||
getCert() {
|
getCert() {
|
||||||
CertBackend.getCert(this.props.account.owner, this.state.certName)
|
CertBackend.getCert(this.state.owner, this.state.certName)
|
||||||
.then((cert) => {
|
.then((cert) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
cert: cert,
|
cert: cert,
|
||||||
@ -48,6 +51,15 @@ class CertEditPage extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getOrganizations() {
|
||||||
|
OrganizationBackend.getOrganizations("admin")
|
||||||
|
.then((res) => {
|
||||||
|
this.setState({
|
||||||
|
organizations: (res.msg === undefined) ? res : [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
parseCertField(key, value) {
|
parseCertField(key, value) {
|
||||||
if (["port"].includes(key)) {
|
if (["port"].includes(key)) {
|
||||||
value = Setting.myParseInt(value);
|
value = Setting.myParseInt(value);
|
||||||
@ -230,7 +242,7 @@ class CertEditPage extends React.Component {
|
|||||||
|
|
||||||
submitCertEdit(willExist) {
|
submitCertEdit(willExist) {
|
||||||
const cert = Setting.deepCopy(this.state.cert);
|
const cert = Setting.deepCopy(this.state.cert);
|
||||||
CertBackend.updateCert(this.state.cert.owner, this.state.certName, cert)
|
CertBackend.updateCert(this.state.owner, this.state.certName, cert)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully saved"));
|
Setting.showMessage("success", i18next.t("general:Successfully saved"));
|
||||||
@ -241,7 +253,7 @@ class CertEditPage extends React.Component {
|
|||||||
if (willExist) {
|
if (willExist) {
|
||||||
this.props.history.push("/certs");
|
this.props.history.push("/certs");
|
||||||
} else {
|
} else {
|
||||||
this.props.history.push(`/certs/${this.state.cert.name}`);
|
this.props.history.push(`/certs/${this.state.cert.owner}/${this.state.cert.name}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
|
||||||
|
@ -23,10 +23,20 @@ import BaseListPage from "./BaseListPage";
|
|||||||
import PopconfirmModal from "./common/modal/PopconfirmModal";
|
import PopconfirmModal from "./common/modal/PopconfirmModal";
|
||||||
|
|
||||||
class CertListPage extends BaseListPage {
|
class CertListPage extends BaseListPage {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.setState({
|
||||||
|
owner: Setting.isAdminUser(this.props.account) ? "admin" : this.props.account.owner,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
newCert() {
|
newCert() {
|
||||||
const randomName = Setting.getRandomName();
|
const randomName = Setting.getRandomName();
|
||||||
return {
|
return {
|
||||||
owner: this.props.account.owner, // this.props.account.certname,
|
owner: this.state.owner,
|
||||||
name: `cert_${randomName}`,
|
name: `cert_${randomName}`,
|
||||||
createdTime: moment().format(),
|
createdTime: moment().format(),
|
||||||
displayName: `New Cert - ${randomName}`,
|
displayName: `New Cert - ${randomName}`,
|
||||||
@ -45,7 +55,7 @@ class CertListPage extends BaseListPage {
|
|||||||
CertBackend.addCert(newCert)
|
CertBackend.addCert(newCert)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
this.props.history.push({pathname: `/certs/${newCert.name}`, mode: "add"});
|
this.props.history.push({pathname: `/certs/${newCert.owner}/${newCert.name}`, mode: "add"});
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully added"));
|
Setting.showMessage("success", i18next.t("general:Successfully added"));
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
|
||||||
@ -86,7 +96,7 @@ class CertListPage extends BaseListPage {
|
|||||||
...this.getColumnSearchProps("name"),
|
...this.getColumnSearchProps("name"),
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return (
|
return (
|
||||||
<Link to={`/certs/${text}`}>
|
<Link to={`/certs/${record.owner}/${text}`}>
|
||||||
{text}
|
{text}
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
@ -99,6 +109,9 @@ class CertListPage extends BaseListPage {
|
|||||||
width: "150px",
|
width: "150px",
|
||||||
sorter: true,
|
sorter: true,
|
||||||
...this.getColumnSearchProps("organization"),
|
...this.getColumnSearchProps("organization"),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (text !== "admin") ? text : i18next.t("provider:admin (Shared)");
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("general:Created time"),
|
title: i18next.t("general:Created time"),
|
||||||
@ -176,7 +189,7 @@ class CertListPage extends BaseListPage {
|
|||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Button disabled={!Setting.isAdminUser(this.props.account) && (record.owner !== this.props.account.owner)} style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/certs/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
<Button disabled={!Setting.isAdminUser(this.props.account) && (record.owner !== this.props.account.owner)} style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/certs/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||||
<PopconfirmModal
|
<PopconfirmModal
|
||||||
disabled={!Setting.isAdminUser(this.props.account) && (record.owner !== this.props.account.owner)}
|
disabled={!Setting.isAdminUser(this.props.account) && (record.owner !== this.props.account.owner)}
|
||||||
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
||||||
|
@ -16,6 +16,8 @@ import React from "react";
|
|||||||
import {Redirect, Route, Switch} from "react-router-dom";
|
import {Redirect, Route, Switch} from "react-router-dom";
|
||||||
import {Spin} from "antd";
|
import {Spin} from "antd";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
||||||
|
import PricingPage from "./pricing/PricingPage";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import * as Conf from "./Conf";
|
import * as Conf from "./Conf";
|
||||||
import SignupPage from "./auth/SignupPage";
|
import SignupPage from "./auth/SignupPage";
|
||||||
@ -33,6 +35,7 @@ class EntryPage extends React.Component {
|
|||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
application: undefined,
|
application: undefined,
|
||||||
|
pricing: undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,9 +68,23 @@ class EntryPage extends React.Component {
|
|||||||
this.props.updataThemeData(themeData);
|
this.props.updataThemeData(themeData);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onUpdatePricing = (pricing) => {
|
||||||
|
this.setState({
|
||||||
|
pricing: pricing,
|
||||||
|
});
|
||||||
|
|
||||||
|
ApplicationBackend.getApplication("admin", pricing.application)
|
||||||
|
.then((application) => {
|
||||||
|
const themeData = application !== null ? Setting.getThemeData(application.organizationObj, application) : Conf.ThemeDefault;
|
||||||
|
this.props.updataThemeData(themeData);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="loginBackground" style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${this.state.application?.formBackgroundUrl})`}}>
|
<div className="loginBackground"
|
||||||
<Spin size="large" spinning={this.state.application === undefined} tip={i18next.t("login:Loading")} style={{margin: "0 auto"}} />
|
style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${this.state.application?.formBackgroundUrl})`}}>
|
||||||
|
<Spin size="large" spinning={this.state.application === undefined && this.state.pricing === undefined} tip={i18next.t("login:Loading")}
|
||||||
|
style={{margin: "0 auto"}} />
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} applicationName={authConfig.appName} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
<Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} applicationName={authConfig.appName} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||||
<Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
<Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||||
@ -85,6 +102,7 @@ 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="/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/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="/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/:pricingName" render={(props) => this.renderHomeIfLoggedIn(<PricingPage {...this.props} pricing={this.state.pricing} onUpdatePricing={onUpdatePricing} {...props} />)} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -185,6 +185,7 @@ class LdapEditPage extends React.Component {
|
|||||||
{value: "uid", label: "uid"},
|
{value: "uid", label: "uid"},
|
||||||
{value: "mail", label: "Email"},
|
{value: "mail", label: "Email"},
|
||||||
{value: "mobile", label: "mobile"},
|
{value: "mobile", label: "mobile"},
|
||||||
|
{value: "sAMAccountName", label: "sAMAccountName"},
|
||||||
].map((item) => Setting.getOption(item.label, item.value))} onChange={value => {
|
].map((item) => Setting.getOption(item.label, item.value))} onChange={value => {
|
||||||
this.updateLdapField("filterFields", value);
|
this.updateLdapField("filterFields", value);
|
||||||
}} />
|
}} />
|
||||||
|
@ -94,7 +94,7 @@ class LdapSyncPage extends React.Component {
|
|||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
this.setState((prevState) => {
|
this.setState((prevState) => {
|
||||||
prevState.users = res.data.users;
|
prevState.users = res.data.users;
|
||||||
prevState.existUuids = res.data2?.length > 0 ? res.data2 : [];
|
prevState.existUuids = res.data.existUuids?.length > 0 ? res.data.existUuids.filter(uuid => uuid !== "") : [];
|
||||||
return prevState;
|
return prevState;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -24,6 +24,7 @@ import {LinkOutlined} from "@ant-design/icons";
|
|||||||
import LdapTable from "./table/LdapTable";
|
import LdapTable from "./table/LdapTable";
|
||||||
import AccountTable from "./table/AccountTable";
|
import AccountTable from "./table/AccountTable";
|
||||||
import ThemeEditor from "./common/theme/ThemeEditor";
|
import ThemeEditor from "./common/theme/ThemeEditor";
|
||||||
|
import MfaTable from "./table/MfaTable";
|
||||||
|
|
||||||
const {Option} = Select;
|
const {Option} = Select;
|
||||||
|
|
||||||
@ -199,6 +200,22 @@ class OrganizationEditPage extends React.Component {
|
|||||||
</Select>
|
</Select>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Languages"), i18next.t("general:Languages - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} mode="tags" style={{width: "100%"}}
|
||||||
|
options={Setting.Countries.map((item) => {
|
||||||
|
return Setting.getOption(item.label, item.key);
|
||||||
|
})}
|
||||||
|
value={this.state.organization.languages ?? []}
|
||||||
|
onChange={(value => {
|
||||||
|
this.updateOrganizationField("languages", value);
|
||||||
|
})} >
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("general:Default avatar"), i18next.t("general:Default avatar - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Default avatar"), i18next.t("general:Default avatar - Tooltip"))} :
|
||||||
@ -258,22 +275,6 @@ class OrganizationEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row style={{marginTop: "20px"}} >
|
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
|
||||||
{Setting.getLabel(i18next.t("general:Languages"), i18next.t("general:Languages - Tooltip"))} :
|
|
||||||
</Col>
|
|
||||||
<Col span={22} >
|
|
||||||
<Select virtual={false} mode="tags" style={{width: "100%"}}
|
|
||||||
options={Setting.Countries.map((item) => {
|
|
||||||
return Setting.getOption(item.label, item.key);
|
|
||||||
})}
|
|
||||||
value={this.state.organization.languages ?? []}
|
|
||||||
onChange={(value => {
|
|
||||||
this.updateOrganizationField("languages", value);
|
|
||||||
})} >
|
|
||||||
</Select>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||||
{Setting.getLabel(i18next.t("organization:Init score"), i18next.t("organization:Init score - Tooltip"))} :
|
{Setting.getLabel(i18next.t("organization:Init score"), i18next.t("organization:Init score - Tooltip"))} :
|
||||||
@ -316,6 +317,18 @@ class OrganizationEditPage extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:MFA items"), i18next.t("general:MFA items - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<MfaTable
|
||||||
|
title={i18next.t("general:MFA items")}
|
||||||
|
table={this.state.organization.mfaItems ?? []}
|
||||||
|
onUpdateTable={(value) => {this.updateOrganizationField("mfaItems", value);}}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("theme:Theme"), i18next.t("theme:Theme - Tooltip"))} :
|
{Setting.getLabel(i18next.t("theme:Theme"), i18next.t("theme:Theme - Tooltip"))} :
|
||||||
|
@ -14,13 +14,14 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Link} from "react-router-dom";
|
import {Link} from "react-router-dom";
|
||||||
import {Button, Switch, Table} from "antd";
|
import {Button, Switch, Table, Upload} from "antd";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import * as PermissionBackend from "./backend/PermissionBackend";
|
import * as PermissionBackend from "./backend/PermissionBackend";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import BaseListPage from "./BaseListPage";
|
import BaseListPage from "./BaseListPage";
|
||||||
import PopconfirmModal from "./common/modal/PopconfirmModal";
|
import PopconfirmModal from "./common/modal/PopconfirmModal";
|
||||||
|
import {UploadOutlined} from "@ant-design/icons";
|
||||||
|
|
||||||
class PermissionListPage extends BaseListPage {
|
class PermissionListPage extends BaseListPage {
|
||||||
newPermission() {
|
newPermission() {
|
||||||
@ -79,6 +80,40 @@ class PermissionListPage extends BaseListPage {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uploadPermissionFile(info) {
|
||||||
|
const {status, response: res} = info.file;
|
||||||
|
if (status === "done") {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
Setting.showMessage("success", "Users uploaded successfully, refreshing the page");
|
||||||
|
|
||||||
|
const {pagination} = this.state;
|
||||||
|
this.fetch({pagination});
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", `Users failed to upload: ${res.msg}`);
|
||||||
|
}
|
||||||
|
} else if (status === "error") {
|
||||||
|
Setting.showMessage("error", "File failed to upload");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
renderPermissionUpload() {
|
||||||
|
const props = {
|
||||||
|
name: "file",
|
||||||
|
accept: ".xlsx",
|
||||||
|
method: "post",
|
||||||
|
action: `${Setting.ServerUrl}/api/upload-permissions`,
|
||||||
|
withCredentials: true,
|
||||||
|
onChange: (info) => {
|
||||||
|
this.uploadPermissionFile(info);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Upload {...props}>
|
||||||
|
<Button type="primary" size="small">
|
||||||
|
<UploadOutlined /> {i18next.t("user:Upload (.xlsx)")}
|
||||||
|
</Button></Upload>
|
||||||
|
);
|
||||||
|
}
|
||||||
renderTable(permissions) {
|
renderTable(permissions) {
|
||||||
const columns = [
|
const columns = [
|
||||||
// https://github.com/ant-design/ant-design/issues/22184
|
// https://github.com/ant-design/ant-design/issues/22184
|
||||||
@ -325,7 +360,10 @@ class PermissionListPage extends BaseListPage {
|
|||||||
title={() => (
|
title={() => (
|
||||||
<div>
|
<div>
|
||||||
{i18next.t("general:Permissions")}
|
{i18next.t("general:Permissions")}
|
||||||
<Button type="primary" size="small" onClick={this.addPermission.bind(this)}>{i18next.t("general:Add")}</Button>
|
<Button style={{marginRight: "5px"}} type="primary" size="small" onClick={this.addPermission.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||||
|
{
|
||||||
|
this.renderPermissionUpload()
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
loading={this.state.loading}
|
loading={this.state.loading}
|
||||||
|
273
web/src/PlanEditPage.js
Normal file
273
web/src/PlanEditPage.js
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import {Button, Card, Col, Input, InputNumber, Row, Select, Switch} from "antd";
|
||||||
|
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||||
|
import * as RoleBackend from "./backend/RoleBackend";
|
||||||
|
import * as PlanBackend from "./backend/PlanBackend";
|
||||||
|
import * as UserBackend from "./backend/UserBackend";
|
||||||
|
import * as Setting from "./Setting";
|
||||||
|
import i18next from "i18next";
|
||||||
|
|
||||||
|
const {Option} = Select;
|
||||||
|
|
||||||
|
class PlanEditPage extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
classes: props,
|
||||||
|
organizationName: props.organizationName !== undefined ? props.organizationName : props.match.params.organizationName,
|
||||||
|
planName: props.match.params.planName,
|
||||||
|
plan: null,
|
||||||
|
organizations: [],
|
||||||
|
users: [],
|
||||||
|
roles: [],
|
||||||
|
providers: [],
|
||||||
|
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
UNSAFE_componentWillMount() {
|
||||||
|
this.getPlan();
|
||||||
|
this.getOrganizations();
|
||||||
|
}
|
||||||
|
|
||||||
|
getPlan() {
|
||||||
|
PlanBackend.getPlan(this.state.organizationName, this.state.planName)
|
||||||
|
.then((plan) => {
|
||||||
|
this.setState({
|
||||||
|
plan: plan,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.getUsers(plan.owner);
|
||||||
|
this.getRoles(plan.owner);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getRoles(organizationName) {
|
||||||
|
RoleBackend.getRoles(organizationName)
|
||||||
|
.then((res) => {
|
||||||
|
this.setState({
|
||||||
|
roles: res,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getUsers(organizationName) {
|
||||||
|
UserBackend.getUsers(organizationName)
|
||||||
|
.then((res) => {
|
||||||
|
this.setState({
|
||||||
|
users: res,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getOrganizations() {
|
||||||
|
OrganizationBackend.getOrganizations("admin")
|
||||||
|
.then((res) => {
|
||||||
|
this.setState({
|
||||||
|
organizations: (res.msg === undefined) ? res : [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
parsePlanField(key, value) {
|
||||||
|
if ([""].includes(key)) {
|
||||||
|
value = Setting.myParseInt(value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePlanField(key, value) {
|
||||||
|
value = this.parsePlanField(key, value);
|
||||||
|
|
||||||
|
const plan = this.state.plan;
|
||||||
|
plan[key] = value;
|
||||||
|
this.setState({
|
||||||
|
plan: plan,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPlan() {
|
||||||
|
return (
|
||||||
|
<Card size="small" title={
|
||||||
|
<div>
|
||||||
|
{this.state.mode === "add" ? i18next.t("plan:New Plan") : i18next.t("plan:Edit Plan")}
|
||||||
|
<Button onClick={() => this.submitPlanEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||||
|
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitPlanEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||||
|
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deletePlan()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||||
|
</div>
|
||||||
|
} style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
|
||||||
|
<Row style={{marginTop: "10px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: "100%"}} value={this.state.plan.owner} onChange={(owner => {
|
||||||
|
this.updatePlanField("owner", owner);
|
||||||
|
this.getUsers(owner);
|
||||||
|
this.getRoles(owner);
|
||||||
|
})}
|
||||||
|
options={this.state.organizations.map((organization) => Setting.getOption(organization.name, organization.name))
|
||||||
|
} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.plan.name} onChange={e => {
|
||||||
|
this.updatePlanField("name", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.plan.displayName} onChange={e => {
|
||||||
|
this.updatePlanField("displayName", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("role:Sub roles"), i18next.t("plan:Sub roles - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: "100%"}} value={this.state.plan.role} onChange={(value => {this.updatePlanField("role", value);})}
|
||||||
|
options={this.state.roles.map((role) => Setting.getOption(`${role.owner}/${role.name}`, `${role.owner}/${role.name}`))
|
||||||
|
} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Description"), i18next.t("general:Description - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.plan.description} onChange={e => {
|
||||||
|
this.updatePlanField("description", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("plan:PricePerMonth"), i18next.t("plan:PricePerMonth - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<InputNumber value={this.state.plan.pricePerMonth} onChange={value => {
|
||||||
|
this.updatePlanField("pricePerMonth", value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("plan:PricePerYear"), i18next.t("plan:PricePerYear - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<InputNumber value={this.state.plan.pricePerYear} onChange={value => {
|
||||||
|
this.updatePlanField("pricePerYear", value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("payment:Currency"), i18next.t("payment:Currency - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: "100%"}} value={this.state.plan.currency} onChange={(value => {
|
||||||
|
this.updatePlanField("currency", value);
|
||||||
|
})}>
|
||||||
|
{
|
||||||
|
[
|
||||||
|
{id: "USD", name: "USD"},
|
||||||
|
{id: "CNY", name: "CNY"},
|
||||||
|
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Is enabled"), i18next.t("general:Is enabled - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={1} >
|
||||||
|
<Switch checked={this.state.plan.isEnabled} onChange={checked => {
|
||||||
|
this.updatePlanField("isEnabled", checked);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
submitPlanEdit(willExist) {
|
||||||
|
const plan = Setting.deepCopy(this.state.plan);
|
||||||
|
PlanBackend.updatePlan(this.state.organizationName, this.state.planName, plan)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
Setting.showMessage("success", i18next.t("general:Successfully saved"));
|
||||||
|
this.setState({
|
||||||
|
planName: this.state.plan.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (willExist) {
|
||||||
|
this.props.history.push("/plans");
|
||||||
|
} else {
|
||||||
|
this.props.history.push(`/plan/${this.state.plan.owner}/${this.state.plan.name}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
|
||||||
|
this.updatePlanField("name", this.state.planName);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deletePlan() {
|
||||||
|
PlanBackend.deletePlan(this.state.plan)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
this.props.history.push("/plans");
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{
|
||||||
|
this.state.plan !== null ? this.renderPlan() : null
|
||||||
|
}
|
||||||
|
<div style={{marginTop: "20px", marginLeft: "40px"}}>
|
||||||
|
<Button size="large" onClick={() => this.submitPlanEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||||
|
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitPlanEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||||
|
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deletePlan()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PlanEditPage;
|
236
web/src/PlanListPage.js
Normal file
236
web/src/PlanListPage.js
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import {Link} from "react-router-dom";
|
||||||
|
import {Button, Switch, Table} from "antd";
|
||||||
|
import moment from "moment";
|
||||||
|
import * as Setting from "./Setting";
|
||||||
|
import * as PlanBackend from "./backend/PlanBackend";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import BaseListPage from "./BaseListPage";
|
||||||
|
import PopconfirmModal from "./common/modal/PopconfirmModal";
|
||||||
|
|
||||||
|
class PlanListPage extends BaseListPage {
|
||||||
|
newPlan() {
|
||||||
|
const randomName = Setting.getRandomName();
|
||||||
|
const owner = (this.state.organizationName !== undefined) ? this.state.organizationName : this.props.account.owner;
|
||||||
|
|
||||||
|
return {
|
||||||
|
owner: owner,
|
||||||
|
name: `plan_${randomName}`,
|
||||||
|
createdTime: moment().format(),
|
||||||
|
pricePerMonth: 10,
|
||||||
|
pricePerYear: 100,
|
||||||
|
currency: "USD",
|
||||||
|
displayName: `New Plan - ${randomName}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
addPlan() {
|
||||||
|
const newPlan = this.newPlan();
|
||||||
|
PlanBackend.addPlan(newPlan)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
this.props.history.push({pathname: `/plan/${newPlan.owner}/${newPlan.name}`, mode: "add"});
|
||||||
|
Setting.showMessage("success", i18next.t("general:Successfully added"));
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deletePlan(i) {
|
||||||
|
PlanBackend.deletePlan(this.state.data[i])
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
|
this.setState({
|
||||||
|
data: Setting.deleteRow(this.state.data, i),
|
||||||
|
pagination: {total: this.state.pagination.total - 1},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTable(plans) {
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Name"),
|
||||||
|
dataIndex: "name",
|
||||||
|
key: "name",
|
||||||
|
width: "140px",
|
||||||
|
fixed: "left",
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps("name"),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Link to={`/plans/${text}`}>
|
||||||
|
{text}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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:Created time"),
|
||||||
|
dataIndex: "createdTime",
|
||||||
|
key: "createdTime",
|
||||||
|
width: "160px",
|
||||||
|
sorter: true,
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return Setting.getFormattedDate(text);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Display name"),
|
||||||
|
dataIndex: "displayName",
|
||||||
|
key: "displayName",
|
||||||
|
width: "170px",
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps("displayName"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("plan:Price per month"),
|
||||||
|
dataIndex: "pricePerMonth",
|
||||||
|
key: "pricePerMonth",
|
||||||
|
width: "130px",
|
||||||
|
...this.getColumnSearchProps("pricePerMonth"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("plan:Price per year"),
|
||||||
|
dataIndex: "pricePerYear",
|
||||||
|
key: "pricePerYear",
|
||||||
|
width: "130px",
|
||||||
|
...this.getColumnSearchProps("pricePerYear"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("plan:Sub role"),
|
||||||
|
dataIndex: "role",
|
||||||
|
key: "role",
|
||||||
|
width: "140px",
|
||||||
|
...this.getColumnSearchProps("role"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Is enabled"),
|
||||||
|
dataIndex: "isEnabled",
|
||||||
|
key: "isEnabled",
|
||||||
|
width: "120px",
|
||||||
|
sorter: true,
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Action"),
|
||||||
|
dataIndex: "",
|
||||||
|
key: "op",
|
||||||
|
width: "200px",
|
||||||
|
fixed: (Setting.isMobile()) ? "false" : "right",
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/plan/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||||
|
<PopconfirmModal
|
||||||
|
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
||||||
|
onConfirm={() => this.deletePlan(index)}
|
||||||
|
>
|
||||||
|
</PopconfirmModal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const paginationProps = {
|
||||||
|
total: this.state.pagination.total,
|
||||||
|
showQuickJumper: true,
|
||||||
|
showSizeChanger: true,
|
||||||
|
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Table scroll={{x: "max-content"}} columns={columns} dataSource={plans} rowKey="name" size="middle" bordered pagination={paginationProps}
|
||||||
|
title={() => (
|
||||||
|
<div>
|
||||||
|
{i18next.t("general:Plans")}
|
||||||
|
<Button type="primary" size="small" onClick={this.addPlan.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
loading={this.state.loading}
|
||||||
|
onChange={this.handleTableChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch = (params = {}) => {
|
||||||
|
let field = params.searchedColumn, value = params.searchText;
|
||||||
|
const sortField = params.sortField, sortOrder = params.sortOrder;
|
||||||
|
if (params.type !== undefined && params.type !== null) {
|
||||||
|
field = "type";
|
||||||
|
value = params.type;
|
||||||
|
}
|
||||||
|
this.setState({loading: true});
|
||||||
|
PlanBackend.getPlans("", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
data: res.data,
|
||||||
|
pagination: {
|
||||||
|
...params.pagination,
|
||||||
|
total: res.data2,
|
||||||
|
},
|
||||||
|
searchText: params.searchText,
|
||||||
|
searchedColumn: params.searchedColumn,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (Setting.isResponseDenied(res)) {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
isAuthorized: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PlanListPage;
|
309
web/src/PricingEditPage.js
Normal file
309
web/src/PricingEditPage.js
Normal file
@ -0,0 +1,309 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import {CopyOutlined} from "@ant-design/icons";
|
||||||
|
import copy from "copy-to-clipboard";
|
||||||
|
import React from "react";
|
||||||
|
import {Button, Card, Col, Input, InputNumber, Row, Select, Switch} from "antd";
|
||||||
|
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
||||||
|
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||||
|
import * as PricingBackend from "./backend/PricingBackend";
|
||||||
|
import * as PlanBackend from "./backend/PlanBackend";
|
||||||
|
import PricingPage from "./pricing/PricingPage";
|
||||||
|
import * as Setting from "./Setting";
|
||||||
|
import i18next from "i18next";
|
||||||
|
|
||||||
|
class PricingEditPage extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
classes: props,
|
||||||
|
organizationName: props.organizationName !== undefined ? props.organizationName : props.match.params.organizationName,
|
||||||
|
pricingName: props.match.params.pricingName,
|
||||||
|
organizations: [],
|
||||||
|
application: null,
|
||||||
|
applications: [],
|
||||||
|
pricing: null,
|
||||||
|
plans: [],
|
||||||
|
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
UNSAFE_componentWillMount() {
|
||||||
|
this.getPricing();
|
||||||
|
this.getOrganizations();
|
||||||
|
this.getApplicationsByOrganization(this.state.organizationName);
|
||||||
|
this.getUserApplication();
|
||||||
|
}
|
||||||
|
|
||||||
|
getPricing() {
|
||||||
|
PricingBackend.getPricing(this.state.organizationName, this.state.pricingName)
|
||||||
|
.then((pricing) => {
|
||||||
|
this.setState({
|
||||||
|
pricing: pricing,
|
||||||
|
});
|
||||||
|
this.getPlans(pricing.owner);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getPlans(organizationName) {
|
||||||
|
PlanBackend.getPlans(organizationName)
|
||||||
|
.then((res) => {
|
||||||
|
this.setState({
|
||||||
|
plans: res,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getOrganizations() {
|
||||||
|
OrganizationBackend.getOrganizations("admin")
|
||||||
|
.then((res) => {
|
||||||
|
this.setState({
|
||||||
|
organizations: (res.msg === undefined) ? res : [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
parsePricingField(key, value) {
|
||||||
|
if ([""].includes(key)) {
|
||||||
|
value = Setting.myParseInt(value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePricingField(key, value) {
|
||||||
|
value = this.parsePricingField(key, value);
|
||||||
|
|
||||||
|
const pricing = this.state.pricing;
|
||||||
|
pricing[key] = value;
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
pricing: pricing,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getApplicationsByOrganization(organizationName) {
|
||||||
|
ApplicationBackend.getApplicationsByOrganization("admin", organizationName)
|
||||||
|
.then((res) => {
|
||||||
|
this.setState({
|
||||||
|
applications: (res.msg === undefined) ? res : [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getUserApplication() {
|
||||||
|
ApplicationBackend.getUserApplication(this.state.organizationName, this.state.userName)
|
||||||
|
.then((application) => {
|
||||||
|
this.setState({
|
||||||
|
application: application,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPricing() {
|
||||||
|
return (
|
||||||
|
<Card size="small" title={
|
||||||
|
<div>
|
||||||
|
{this.state.mode === "add" ? i18next.t("pricing:New Pricing") : i18next.t("pricing:Edit Pricing")}
|
||||||
|
<Button onClick={() => this.submitPricingEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||||
|
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitPricingEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||||
|
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deletePricing()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||||
|
</div>
|
||||||
|
} style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
|
||||||
|
<Row style={{marginTop: "10px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: "100%"}} value={this.state.pricing.owner} onChange={(owner => {
|
||||||
|
this.updatePricingField("owner", owner);
|
||||||
|
this.getApplicationsByOrganization(owner);
|
||||||
|
this.getPlans(owner);
|
||||||
|
})}
|
||||||
|
options={this.state.organizations.map((organization) => Setting.getOption(organization.name, organization.name))
|
||||||
|
} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.pricing.name} onChange={e => {
|
||||||
|
this.updatePricingField("name", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.pricing.displayName} onChange={e => {
|
||||||
|
this.updatePricingField("displayName", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Description"), i18next.t("general:Description - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.pricing.description} onChange={e => {
|
||||||
|
this.updatePricingField("description", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Application"), i18next.t("general:Application - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: "100%"}} value={this.state.pricing.application}
|
||||||
|
onChange={(value => {this.updatePricingField("application", value);})}
|
||||||
|
options={this.state.applications.map((application) => Setting.getOption(application.name, application.name))
|
||||||
|
} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("pricing:Sub plans"), i18next.t("pricing:Sub plans - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select mode="tags" style={{width: "100%"}} value={this.state.pricing.plans}
|
||||||
|
onChange={(value => {
|
||||||
|
this.updatePricingField("plans", value);
|
||||||
|
})}
|
||||||
|
options={this.state.plans.map((plan) => Setting.getOption(`${plan.owner}/${plan.name}`, `${plan.owner}/${plan.name}`))}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("pricing:Has trial"), i18next.t("pricing:Has trial - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={1} >
|
||||||
|
<Switch disabled={true} checked={this.state.pricing.hasTrial} onChange={checked => {
|
||||||
|
this.updatePricingField("hasTrial", checked);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("pricing:Trial duration"), i18next.t("pricing:Trial duration - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<InputNumber min={1} value={this.state.pricing.trialDuration} onChange={value => {
|
||||||
|
this.updatePricingField("trialDuration", value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Is enabled"), i18next.t("general:Is enabled - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={1} >
|
||||||
|
<Switch checked={this.state.pricing.isEnabled} onChange={checked => {
|
||||||
|
this.updatePricingField("isEnabled", checked);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Preview"), i18next.t("general:Preview - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
{
|
||||||
|
this.renderPreview()
|
||||||
|
}
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
submitPricingEdit(willExist) {
|
||||||
|
const pricing = Setting.deepCopy(this.state.pricing);
|
||||||
|
PricingBackend.updatePricing(this.state.organizationName, this.state.pricingName, pricing)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
Setting.showMessage("success", i18next.t("general:Successfully saved"));
|
||||||
|
this.setState({
|
||||||
|
pricingName: this.state.pricing.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (willExist) {
|
||||||
|
this.props.history.push("/pricings");
|
||||||
|
} else {
|
||||||
|
this.props.history.push(`/pricing/${this.state.pricing.owner}/${this.state.pricing.name}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
|
||||||
|
this.updatePricingField("name", this.state.pricingName);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deletePricing() {
|
||||||
|
PricingBackend.deletePricing(this.state.pricing)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
this.props.history.push("/pricings");
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{
|
||||||
|
this.state.pricing !== null ? this.renderPricing() : null
|
||||||
|
}
|
||||||
|
<div style={{marginTop: "20px", marginLeft: "40px"}}>
|
||||||
|
<Button size="large" onClick={() => this.submitPricingEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||||
|
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitPricingEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||||
|
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deletePricing()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPreview() {
|
||||||
|
const pricingUrl = `/select-plan/${this.state.pricing.name}`;
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Col>
|
||||||
|
<Button style={{marginBottom: "10px", marginTop: Setting.isMobile() ? "15px" : "0"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => {
|
||||||
|
copy(`${window.location.origin}${pricingUrl}`);
|
||||||
|
Setting.showMessage("success", i18next.t("pricing:pricing page URL copied to clipboard successfully, please paste it into the incognito window or another browser"));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{i18next.t("pricing:Copy pricing page URL")}
|
||||||
|
</Button>
|
||||||
|
</Col>
|
||||||
|
<Col>
|
||||||
|
<PricingPage pricing={this.state.pricing}></PricingPage>
|
||||||
|
</Col>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PricingEditPage;
|
217
web/src/PricingListPage.js
Normal file
217
web/src/PricingListPage.js
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import {Link} from "react-router-dom";
|
||||||
|
import {Button, Switch, Table} from "antd";
|
||||||
|
import moment from "moment";
|
||||||
|
import * as Setting from "./Setting";
|
||||||
|
import * as PricingBackend from "./backend/PricingBackend";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import BaseListPage from "./BaseListPage";
|
||||||
|
import PopconfirmModal from "./common/modal/PopconfirmModal";
|
||||||
|
|
||||||
|
class PricingListPage extends BaseListPage {
|
||||||
|
newPricing() {
|
||||||
|
const randomName = Setting.getRandomName();
|
||||||
|
const owner = (this.state.organizationName !== undefined) ? this.state.organizationName : this.props.account.owner;
|
||||||
|
|
||||||
|
return {
|
||||||
|
owner: owner,
|
||||||
|
name: `pricing_${randomName}`,
|
||||||
|
createdTime: moment().format(),
|
||||||
|
plans: [],
|
||||||
|
displayName: `New Pricing - ${randomName}`,
|
||||||
|
hasTrial: true,
|
||||||
|
isEnabled: true,
|
||||||
|
trialDuration: 14,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
addPricing() {
|
||||||
|
const newPricing = this.newPricing();
|
||||||
|
PricingBackend.addPricing(newPricing)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
this.props.history.push({pathname: `/pricing/${newPricing.owner}/${newPricing.name}`, mode: "add"});
|
||||||
|
Setting.showMessage("success", i18next.t("general:Successfully added"));
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deletePricing(i) {
|
||||||
|
PricingBackend.deletePricing(this.state.data[i])
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
|
this.setState({
|
||||||
|
data: Setting.deleteRow(this.state.data, i),
|
||||||
|
pagination: {total: this.state.pagination.total - 1},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTable(pricings) {
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Name"),
|
||||||
|
dataIndex: "name",
|
||||||
|
key: "name",
|
||||||
|
width: "140px",
|
||||||
|
fixed: "left",
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps("name"),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Link to={`/pricing/${record.owner}/${text}`}>
|
||||||
|
{text}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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:Created time"),
|
||||||
|
dataIndex: "createdTime",
|
||||||
|
key: "createdTime",
|
||||||
|
width: "160px",
|
||||||
|
sorter: true,
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return Setting.getFormattedDate(text);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Display name"),
|
||||||
|
dataIndex: "displayName",
|
||||||
|
key: "displayName",
|
||||||
|
width: "170px",
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps("displayName"),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Is enabled"),
|
||||||
|
dataIndex: "isEnabled",
|
||||||
|
key: "isEnabled",
|
||||||
|
width: "120px",
|
||||||
|
sorter: true,
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Action"),
|
||||||
|
dataIndex: "",
|
||||||
|
key: "op",
|
||||||
|
width: "230px",
|
||||||
|
fixed: (Setting.isMobile()) ? "false" : "right",
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/pricing/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||||
|
<PopconfirmModal
|
||||||
|
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
||||||
|
onConfirm={() => this.deletePricing(index)}
|
||||||
|
>
|
||||||
|
</PopconfirmModal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const paginationProps = {
|
||||||
|
total: this.state.pagination.total,
|
||||||
|
showQuickJumper: true,
|
||||||
|
showSizeChanger: true,
|
||||||
|
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Table scroll={{x: "max-content"}} columns={columns} dataSource={pricings} rowKey="name" size="middle" bordered pagination={paginationProps}
|
||||||
|
title={() => (
|
||||||
|
<div>
|
||||||
|
{i18next.t("general:Pricings")}
|
||||||
|
<Button type="primary" size="small" onClick={this.addPricing.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
loading={this.state.loading}
|
||||||
|
onChange={this.handleTableChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch = (params = {}) => {
|
||||||
|
let field = params.searchedColumn, value = params.searchText;
|
||||||
|
const sortField = params.sortField, sortOrder = params.sortOrder;
|
||||||
|
if (params.type !== undefined && params.type !== null) {
|
||||||
|
field = "type";
|
||||||
|
value = params.type;
|
||||||
|
}
|
||||||
|
this.setState({loading: true});
|
||||||
|
PricingBackend.getPricings("", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
data: res.data,
|
||||||
|
pagination: {
|
||||||
|
...params.pagination,
|
||||||
|
total: res.data2,
|
||||||
|
},
|
||||||
|
searchText: params.searchText,
|
||||||
|
searchedColumn: params.searchedColumn,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (Setting.isResponseDenied(res)) {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
isAuthorized: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PricingListPage;
|
@ -856,7 +856,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
this.state.provider.type === "WeChat Pay" ? (
|
this.state.provider.type === "WeChat Pay" ? (
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel("cert", "cert")} :
|
{Setting.getLabel(i18next.t("general:Cert"), i18next.t("general:Cert - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Input value={this.state.provider.cert} onChange={e => {
|
<Input value={this.state.provider.cert} onChange={e => {
|
||||||
|
@ -112,6 +112,9 @@ class ProviderListPage extends BaseListPage {
|
|||||||
width: "150px",
|
width: "150px",
|
||||||
sorter: true,
|
sorter: true,
|
||||||
...this.getColumnSearchProps("organization"),
|
...this.getColumnSearchProps("organization"),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (text !== "admin") ? text : i18next.t("provider:admin (Shared)");
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("general:Created time"),
|
title: i18next.t("general:Created time"),
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Button, Table, Upload} from "antd";
|
import {Button, Image, Table, Upload} from "antd";
|
||||||
import {UploadOutlined} from "@ant-design/icons";
|
import {UploadOutlined} from "@ant-design/icons";
|
||||||
import copy from "copy-to-clipboard";
|
import copy from "copy-to-clipboard";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
@ -61,7 +61,9 @@ class ResourceListPage extends BaseListPage {
|
|||||||
.then(res => {
|
.then(res => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("application:File uploaded successfully"));
|
Setting.showMessage("success", i18next.t("application:File uploaded successfully"));
|
||||||
window.location.reload();
|
|
||||||
|
const {pagination} = this.state;
|
||||||
|
this.fetch({pagination});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", res.msg);
|
Setting.showMessage("error", res.msg);
|
||||||
}
|
}
|
||||||
@ -88,7 +90,6 @@ class ResourceListPage extends BaseListPage {
|
|||||||
dataIndex: "provider",
|
dataIndex: "provider",
|
||||||
key: "provider",
|
key: "provider",
|
||||||
width: "150px",
|
width: "150px",
|
||||||
fixed: "left",
|
|
||||||
sorter: true,
|
sorter: true,
|
||||||
...this.getColumnSearchProps("provider"),
|
...this.getColumnSearchProps("provider"),
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
@ -99,6 +100,21 @@ class ResourceListPage 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:Application"),
|
title: i18next.t("general:Application"),
|
||||||
dataIndex: "application",
|
dataIndex: "application",
|
||||||
@ -201,12 +217,16 @@ class ResourceListPage extends BaseListPage {
|
|||||||
dataIndex: "preview",
|
dataIndex: "preview",
|
||||||
key: "preview",
|
key: "preview",
|
||||||
width: "100px",
|
width: "100px",
|
||||||
|
fixed: (Setting.isMobile()) ? "false" : "right",
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
if (record.fileType === "image") {
|
if (record.fileType === "image") {
|
||||||
|
const errorImage = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMIAAADDCAYAAADQvc6UAAABRWlDQ1BJQ0MgUHJvZmlsZQAAKJFjYGASSSwoyGFhYGDIzSspCnJ3UoiIjFJgf8LAwSDCIMogwMCcmFxc4BgQ4ANUwgCjUcG3awyMIPqyLsis7PPOq3QdDFcvjV3jOD1boQVTPQrgSkktTgbSf4A4LbmgqISBgTEFyFYuLykAsTuAbJEioKOA7DkgdjqEvQHEToKwj4DVhAQ5A9k3gGyB5IxEoBmML4BsnSQk8XQkNtReEOBxcfXxUQg1Mjc0dyHgXNJBSWpFCYh2zi+oLMpMzyhRcASGUqqCZ16yno6CkYGRAQMDKMwhqj/fAIcloxgHQqxAjIHBEugw5sUIsSQpBobtQPdLciLEVJYzMPBHMDBsayhILEqEO4DxG0txmrERhM29nYGBddr//5/DGRjYNRkY/l7////39v///y4Dmn+LgeHANwDrkl1AuO+pmgAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAwqADAAQAAAABAAAAwwAAAAD9b/HnAAAHlklEQVR4Ae3dP3PTWBSGcbGzM6GCKqlIBRV0dHRJFarQ0eUT8LH4BnRU0NHR0UEFVdIlFRV7TzRksomPY8uykTk/zewQfKw/9znv4yvJynLv4uLiV2dBoDiBf4qP3/ARuCRABEFAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghgg0Aj8i0JO4OzsrPv69Wv+hi2qPHr0qNvf39+iI97soRIh4f3z58/u7du3SXX7Xt7Z2enevHmzfQe+oSN2apSAPj09TSrb+XKI/f379+08+A0cNRE2ANkupk+ACNPvkSPcAAEibACyXUyfABGm3yNHuAECRNgAZLuYPgEirKlHu7u7XdyytGwHAd8jjNyng4OD7vnz51dbPT8/7z58+NB9+/bt6jU/TI+AGWHEnrx48eJ/EsSmHzx40L18+fLyzxF3ZVMjEyDCiEDjMYZZS5wiPXnyZFbJaxMhQIQRGzHvWR7XCyOCXsOmiDAi1HmPMMQjDpbpEiDCiL358eNHurW/5SnWdIBbXiDCiA38/Pnzrce2YyZ4//59F3ePLNMl4PbpiL2J0L979+7yDtHDhw8vtzzvdGnEXdvUigSIsCLAWavHp/+qM0BcXMd/q25n1vF57TYBp0a3mUzilePj4+7k5KSLb6gt6ydAhPUzXnoPR0dHl79WGTNCfBnn1uvSCJdegQhLI1vvCk+fPu2ePXt2tZOYEV6/fn31dz+shwAR1sP1cqvLntbEN9MxA9xcYjsxS1jWR4AIa2Ibzx0tc44fYX/16lV6NDFLXH+YL32jwiACRBiEbf5KcXoTIsQSpzXx4N28Ja4BQoK7rgXiydbHjx/P25TaQAJEGAguWy0+2Q8PD6/Ki4R8EVl+bzBOnZY95fq9rj9zAkTI2SxdidBHqG9+skdw43borCXO/ZcJdraPWdv22uIEiLA4q7nvvCug8WTqzQveOH26fodo7g6uFe/a17W3+nFBAkRYENRdb1vkkz1CH9cPsVy/jrhr27PqMYvENYNlHAIesRiBYwRy0V+8iXP8+/fvX11Mr7L7ECueb/r48eMqm7FuI2BGWDEG8cm+7G3NEOfmdcTQw4h9/55lhm7DekRYKQPZF2ArbXTAyu4kDYB2YxUzwg0gi/41ztHnfQG26HbGel/crVrm7tNY+/1btkOEAZ2M05r4FB7r9GbAIdxaZYrHdOsgJ/wCEQY0J74TmOKnbxxT9n3FgGGWWsVdowHtjt9Nnvf7yQM2aZU/TIAIAxrw6dOnAWtZZcoEnBpNuTuObWMEiLAx1HY0ZQJEmHJ3HNvGCBBhY6jtaMoEiJB0Z29vL6ls58vxPcO8/zfrdo5qvKO+d3Fx8Wu8zf1dW4p/cPzLly/dtv9Ts/EbcvGAHhHyfBIhZ6NSiIBTo0LNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiEC/wGgKKC4YMA4TAAAAABJRU5ErkJggg==";
|
||||||
return (
|
return (
|
||||||
<a target="_blank" rel="noreferrer" href={record.url}>
|
<Image
|
||||||
<img src={record.url} alt={record.name} width={200} />
|
width={200}
|
||||||
</a>
|
src={record.url}
|
||||||
|
fallback={errorImage}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
} else if (record.fileType === "video") {
|
} else if (record.fileType === "video") {
|
||||||
return (
|
return (
|
||||||
@ -222,10 +242,11 @@ class ResourceListPage extends BaseListPage {
|
|||||||
dataIndex: "url",
|
dataIndex: "url",
|
||||||
key: "url",
|
key: "url",
|
||||||
width: "120px",
|
width: "120px",
|
||||||
|
fixed: (Setting.isMobile()) ? "false" : "right",
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Button type="normal" onClick={() => {
|
<Button onClick={() => {
|
||||||
copy(record.url);
|
copy(record.url);
|
||||||
Setting.showMessage("success", i18next.t("provider:Link copied to clipboard successfully"));
|
Setting.showMessage("success", i18next.t("provider:Link copied to clipboard successfully"));
|
||||||
}}
|
}}
|
||||||
@ -288,7 +309,7 @@ class ResourceListPage extends BaseListPage {
|
|||||||
const field = params.searchedColumn, value = params.searchText;
|
const field = params.searchedColumn, value = params.searchText;
|
||||||
const sortField = params.sortField, sortOrder = params.sortOrder;
|
const sortField = params.sortField, sortOrder = params.sortOrder;
|
||||||
this.setState({loading: true});
|
this.setState({loading: true});
|
||||||
ResourceBackend.getResources("admin", this.props.account.name, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
|
ResourceBackend.getResources(Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, this.props.account.name, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -14,13 +14,14 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Link} from "react-router-dom";
|
import {Link} from "react-router-dom";
|
||||||
import {Button, Switch, Table} from "antd";
|
import {Button, Switch, Table, Upload} from "antd";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import * as RoleBackend from "./backend/RoleBackend";
|
import * as RoleBackend from "./backend/RoleBackend";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import BaseListPage from "./BaseListPage";
|
import BaseListPage from "./BaseListPage";
|
||||||
import PopconfirmModal from "./common/modal/PopconfirmModal";
|
import PopconfirmModal from "./common/modal/PopconfirmModal";
|
||||||
|
import {UploadOutlined} from "@ant-design/icons";
|
||||||
|
|
||||||
class RoleListPage extends BaseListPage {
|
class RoleListPage extends BaseListPage {
|
||||||
newRole() {
|
newRole() {
|
||||||
@ -71,6 +72,42 @@ class RoleListPage extends BaseListPage {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uploadRoleFile(info) {
|
||||||
|
const {status, response: res} = info.file;
|
||||||
|
if (status === "done") {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
Setting.showMessage("success", "Users uploaded successfully, refreshing the page");
|
||||||
|
|
||||||
|
const {pagination} = this.state;
|
||||||
|
this.fetch({pagination});
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", `Users failed to upload: ${res.msg}`);
|
||||||
|
}
|
||||||
|
} else if (status === "error") {
|
||||||
|
Setting.showMessage("error", "File failed to upload");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderRoleUpload() {
|
||||||
|
const props = {
|
||||||
|
name: "file",
|
||||||
|
accept: ".xlsx",
|
||||||
|
method: "post",
|
||||||
|
action: `${Setting.ServerUrl}/api/upload-roles`,
|
||||||
|
withCredentials: true,
|
||||||
|
onChange: (info) => {
|
||||||
|
this.uploadRoleFile(info);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Upload {...props}>
|
||||||
|
<Button type="primary" size="small">
|
||||||
|
<UploadOutlined /> {i18next.t("user:Upload (.xlsx)")}
|
||||||
|
</Button>
|
||||||
|
</Upload>
|
||||||
|
);
|
||||||
|
}
|
||||||
renderTable(roles) {
|
renderTable(roles) {
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
@ -200,7 +237,10 @@ class RoleListPage extends BaseListPage {
|
|||||||
title={() => (
|
title={() => (
|
||||||
<div>
|
<div>
|
||||||
{i18next.t("general:Roles")}
|
{i18next.t("general:Roles")}
|
||||||
<Button type="primary" size="small" onClick={this.addRole.bind(this)}>{i18next.t("general:Add")}</Button>
|
<Button style={{marginRight: "5px"}} type="primary" size="small" onClick={this.addRole.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||||
|
{
|
||||||
|
this.renderRoleUpload()
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
loading={this.state.loading}
|
loading={this.state.loading}
|
||||||
|
@ -24,6 +24,7 @@ import {authConfig} from "./auth/Auth";
|
|||||||
import {Helmet} from "react-helmet";
|
import {Helmet} from "react-helmet";
|
||||||
import * as Conf from "./Conf";
|
import * as Conf from "./Conf";
|
||||||
import * as phoneNumber from "libphonenumber-js";
|
import * as phoneNumber from "libphonenumber-js";
|
||||||
|
import moment from "moment";
|
||||||
|
|
||||||
const {Option} = Select;
|
const {Option} = Select;
|
||||||
|
|
||||||
@ -42,6 +43,7 @@ export const Countries = [{label: "English", key: "en", country: "US", alt: "Eng
|
|||||||
{label: "한국어", key: "ko", country: "KR", alt: "한국어"},
|
{label: "한국어", key: "ko", country: "KR", alt: "한국어"},
|
||||||
{label: "Русский", key: "ru", country: "RU", alt: "Русский"},
|
{label: "Русский", key: "ru", country: "RU", alt: "Русский"},
|
||||||
{label: "TiếngViệt", key: "vi", country: "VN", alt: "TiếngViệt"},
|
{label: "TiếngViệt", key: "vi", country: "VN", alt: "TiếngViệt"},
|
||||||
|
{label: "Português", key: "pt", country: "BR", alt: "Português"},
|
||||||
];
|
];
|
||||||
|
|
||||||
export function getThemeData(organization, application) {
|
export function getThemeData(organization, application) {
|
||||||
@ -325,19 +327,19 @@ export function isSignupItemPrompted(signupItem) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getAllPromptedProviderItems(application) {
|
export function getAllPromptedProviderItems(application) {
|
||||||
return application.providers.filter(providerItem => isProviderPrompted(providerItem));
|
return application.providers?.filter(providerItem => isProviderPrompted(providerItem));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAllPromptedSignupItems(application) {
|
export function getAllPromptedSignupItems(application) {
|
||||||
return application.signupItems.filter(signupItem => isSignupItemPrompted(signupItem));
|
return application.signupItems?.filter(signupItem => isSignupItemPrompted(signupItem));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSignupItem(application, itemName) {
|
export function getSignupItem(application, itemName) {
|
||||||
const signupItems = application.signupItems?.filter(signupItem => signupItem.name === itemName);
|
const signupItems = application.signupItems?.filter(signupItem => signupItem.name === itemName);
|
||||||
if (signupItems.length === 0) {
|
if (signupItems?.length > 0) {
|
||||||
return null;
|
return signupItems[0];
|
||||||
}
|
}
|
||||||
return signupItems[0];
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isValidPersonName(personName) {
|
export function isValidPersonName(personName) {
|
||||||
@ -409,12 +411,12 @@ export function isAffiliationPrompted(application) {
|
|||||||
|
|
||||||
export function hasPromptPage(application) {
|
export function hasPromptPage(application) {
|
||||||
const providerItems = getAllPromptedProviderItems(application);
|
const providerItems = getAllPromptedProviderItems(application);
|
||||||
if (providerItems.length !== 0) {
|
if (providerItems?.length > 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const signupItems = getAllPromptedSignupItems(application);
|
const signupItems = getAllPromptedSignupItems(application);
|
||||||
if (signupItems.length !== 0) {
|
if (signupItems?.length > 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -591,9 +593,8 @@ export function getFormattedDate(date) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
date = date.replace("T", " ");
|
const m = moment(date).local();
|
||||||
date = date.replace("+08:00", " ");
|
return m.format("YYYY-MM-DD HH:mm:ss");
|
||||||
return date;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getFormattedDateShort(date) {
|
export function getFormattedDateShort(date) {
|
||||||
|
333
web/src/SubscriptionEditPage.js
Normal file
333
web/src/SubscriptionEditPage.js
Normal file
@ -0,0 +1,333 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import moment from "moment";
|
||||||
|
import React from "react";
|
||||||
|
import {Button, Card, Col, DatePicker, Input, InputNumber, Row, Select, Switch} from "antd";
|
||||||
|
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||||
|
import * as PlanBackend from "./backend/PlanBackend";
|
||||||
|
import * as SubscriptionBackend from "./backend/SubscriptionBackend";
|
||||||
|
import * as UserBackend from "./backend/UserBackend";
|
||||||
|
import * as Setting from "./Setting";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
|
class SubscriptionEditPage extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
classes: props,
|
||||||
|
organizationName: props.organizationName !== undefined ? props.organizationName : props.match.params.organizationName,
|
||||||
|
subscriptionName: props.match.params.subscriptionName,
|
||||||
|
subscription: null,
|
||||||
|
organizations: [],
|
||||||
|
users: [],
|
||||||
|
planes: [],
|
||||||
|
providers: [],
|
||||||
|
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
UNSAFE_componentWillMount() {
|
||||||
|
this.getSubscription();
|
||||||
|
this.getOrganizations();
|
||||||
|
}
|
||||||
|
|
||||||
|
getSubscription() {
|
||||||
|
SubscriptionBackend.getSubscription(this.state.organizationName, this.state.subscriptionName)
|
||||||
|
.then((subscription) => {
|
||||||
|
this.setState({
|
||||||
|
subscription: subscription,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.getUsers(subscription.owner);
|
||||||
|
this.getPlanes(subscription.owner);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getPlanes(organizationName) {
|
||||||
|
PlanBackend.getPlans(organizationName)
|
||||||
|
.then((res) => {
|
||||||
|
this.setState({
|
||||||
|
planes: res,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getUsers(organizationName) {
|
||||||
|
UserBackend.getUsers(organizationName)
|
||||||
|
.then((res) => {
|
||||||
|
this.setState({
|
||||||
|
users: res,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getOrganizations() {
|
||||||
|
OrganizationBackend.getOrganizations("admin")
|
||||||
|
.then((res) => {
|
||||||
|
this.setState({
|
||||||
|
organizations: (res.msg === undefined) ? res : [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
parseSubscriptionField(key, value) {
|
||||||
|
if ([""].includes(key)) {
|
||||||
|
value = Setting.myParseInt(value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSubscriptionField(key, value) {
|
||||||
|
value = this.parseSubscriptionField(key, value);
|
||||||
|
|
||||||
|
const subscription = this.state.subscription;
|
||||||
|
subscription[key] = value;
|
||||||
|
this.setState({
|
||||||
|
subscription: subscription,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderSubscription() {
|
||||||
|
return (
|
||||||
|
<Card size="small" title={
|
||||||
|
<div>
|
||||||
|
{this.state.mode === "add" ? i18next.t("subscription:New Subscription") : i18next.t("subscription:Edit Subscription")}
|
||||||
|
<Button onClick={() => this.submitSubscriptionEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||||
|
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitSubscriptionEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||||
|
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteSubscription()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||||
|
</div>
|
||||||
|
} style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
|
||||||
|
<Row style={{marginTop: "10px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: "100%"}} value={this.state.subscription.owner} onChange={(owner => {
|
||||||
|
this.updateSubscriptionField("owner", owner);
|
||||||
|
this.getUsers(owner);
|
||||||
|
this.getPlanes(owner);
|
||||||
|
})}
|
||||||
|
options={this.state.organizations.map((organization) => Setting.getOption(organization.name, organization.name))
|
||||||
|
} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.subscription.name} onChange={e => {
|
||||||
|
this.updateSubscriptionField("name", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.subscription.displayName} onChange={e => {
|
||||||
|
this.updateSubscriptionField("displayName", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("subscription:Duration"), i18next.t("subscription:Duration - Tooltip"))}
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<InputNumber value={this.state.subscription.duration} onChange={value => {
|
||||||
|
this.updateSubscriptionField("duration", value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("subscription:Start Date"), i18next.t("subscription:Start Date - Tooltip"))}
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<DatePicker value={dayjs(this.state.subscription.startDate)} onChange={value => {
|
||||||
|
this.updateSubscriptionField("startDate", value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("subscription:End Date"), i18next.t("subscription:End Date - Tooltip"))}
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<DatePicker value={dayjs(this.state.subscription.endDate)} onChange={value => {
|
||||||
|
this.updateSubscriptionField("endDate", value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("subscription:Sub users"), i18next.t("subscription:Sub users - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select style={{width: "100%"}} value={this.state.subscription.user}
|
||||||
|
onChange={(value => {this.updateSubscriptionField("user", value);})}
|
||||||
|
options={this.state.users.map((user) => Setting.getOption(`${user.owner}/${user.name}`, `${user.owner}/${user.name}`))}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("subscription:Sub plan"), i18next.t("subscription:Sub plan - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: "100%"}} value={this.state.subscription.plan} onChange={(value => {this.updateSubscriptionField("plan", value);})}
|
||||||
|
options={this.state.planes.map((plan) => Setting.getOption(`${plan.owner}/${plan.name}`, `${plan.owner}/${plan.name}`))
|
||||||
|
} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Description"), i18next.t("general:Description - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.subscription.description} onChange={e => {
|
||||||
|
this.updateSubscriptionField("description", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Is enabled"), i18next.t("general:Is enabled - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={1} >
|
||||||
|
<Switch checked={this.state.subscription.isEnabled} onChange={checked => {
|
||||||
|
this.updateSubscriptionField("isEnabled", checked);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Submitter"), i18next.t("general:Submitter - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input disabled={true} value={this.state.subscription.submitter} onChange={e => {
|
||||||
|
this.updateSubscriptionField("submitter", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Approver"), i18next.t("general:Approver - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input disabled={true} value={this.state.subscription.approver} onChange={e => {
|
||||||
|
this.updateSubscriptionField("approver", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Approve time"), i18next.t("general:Approve time - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input disabled={true} value={Setting.getFormattedDate(this.state.subscription.approveTime)} onChange={e => {
|
||||||
|
this.updatePermissionField("approveTime", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:State"), i18next.t("general:State - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} disabled={!Setting.isLocalAdminUser(this.props.account)} style={{width: "100%"}} value={this.state.subscription.state} onChange={(value => {
|
||||||
|
if (this.state.subscription.state !== value) {
|
||||||
|
if (value === "Approved") {
|
||||||
|
this.updateSubscriptionField("approver", this.props.account.name);
|
||||||
|
this.updateSubscriptionField("approveTime", moment().format());
|
||||||
|
} else {
|
||||||
|
this.updateSubscriptionField("approver", "");
|
||||||
|
this.updateSubscriptionField("approveTime", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateSubscriptionField("state", value);
|
||||||
|
})}
|
||||||
|
options={[
|
||||||
|
{value: "Approved", name: i18next.t("subscription:Approved")},
|
||||||
|
{value: "Pending", name: i18next.t("subscription:Pending")},
|
||||||
|
].map((item) => Setting.getOption(item.name, item.value))}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
submitSubscriptionEdit(willExist) {
|
||||||
|
const subscription = Setting.deepCopy(this.state.subscription);
|
||||||
|
SubscriptionBackend.updateSubscription(this.state.organizationName, this.state.subscriptionName, subscription)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
Setting.showMessage("success", i18next.t("general:Successfully saved"));
|
||||||
|
this.setState({
|
||||||
|
subscriptionName: this.state.subscription.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (willExist) {
|
||||||
|
this.props.history.push("/subscriptions");
|
||||||
|
} else {
|
||||||
|
this.props.history.push(`/subscription/${this.state.subscription.owner}/${this.state.subscription.name}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
|
||||||
|
this.updateSubscriptionField("name", this.state.subscriptionName);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteSubscription() {
|
||||||
|
SubscriptionBackend.deleteSubscription(this.state.subscription)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
this.props.history.push("/subscriptions");
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{
|
||||||
|
this.state.subscription !== null ? this.renderSubscription() : null
|
||||||
|
}
|
||||||
|
<div style={{marginTop: "20px", marginLeft: "40px"}}>
|
||||||
|
<Button size="large" onClick={() => this.submitSubscriptionEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||||
|
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitSubscriptionEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||||
|
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteSubscription()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SubscriptionEditPage;
|
239
web/src/SubscriptionListPage.js
Normal file
239
web/src/SubscriptionListPage.js
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import {Link} from "react-router-dom";
|
||||||
|
import {Button, Table} from "antd";
|
||||||
|
import moment from "moment";
|
||||||
|
import * as Setting from "./Setting";
|
||||||
|
import * as SubscriptionBackend from "./backend/SubscriptionBackend";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import BaseListPage from "./BaseListPage";
|
||||||
|
import PopconfirmModal from "./common/modal/PopconfirmModal";
|
||||||
|
|
||||||
|
class SubscriptionListPage extends BaseListPage {
|
||||||
|
newSubscription() {
|
||||||
|
const randomName = Setting.getRandomName();
|
||||||
|
const owner = (this.state.organizationName !== undefined) ? this.state.organizationName : this.props.account.owner;
|
||||||
|
const defaultDuration = 365;
|
||||||
|
|
||||||
|
return {
|
||||||
|
owner: owner,
|
||||||
|
name: `subscription_${randomName}`,
|
||||||
|
createdTime: moment().format(),
|
||||||
|
startDate: moment().format(),
|
||||||
|
endDate: moment().add(defaultDuration, "d").format(),
|
||||||
|
displayName: `New Subscription - ${randomName}`,
|
||||||
|
tag: "",
|
||||||
|
users: [],
|
||||||
|
expireInDays: defaultDuration,
|
||||||
|
submitter: this.props.account.name,
|
||||||
|
approver: "",
|
||||||
|
approveTime: "",
|
||||||
|
state: "Pending",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
addSubscription() {
|
||||||
|
const newSubscription = this.newSubscription();
|
||||||
|
SubscriptionBackend.addSubscription(newSubscription)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
this.props.history.push({pathname: `/subscription/${newSubscription.owner}/${newSubscription.name}`, mode: "add"});
|
||||||
|
Setting.showMessage("success", i18next.t("general:Successfully added"));
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteSubscription(i) {
|
||||||
|
SubscriptionBackend.deleteSubscription(this.state.data[i])
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
|
this.setState({
|
||||||
|
data: Setting.deleteRow(this.state.data, i),
|
||||||
|
pagination: {total: this.state.pagination.total - 1},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTable(subscriptions) {
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Name"),
|
||||||
|
dataIndex: "name",
|
||||||
|
key: "name",
|
||||||
|
width: "140px",
|
||||||
|
fixed: "left",
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps("name"),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Link to={`/subscriptions/${text}`}>
|
||||||
|
{text}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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:Created time"),
|
||||||
|
dataIndex: "createdTime",
|
||||||
|
key: "createdTime",
|
||||||
|
width: "160px",
|
||||||
|
sorter: true,
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return Setting.getFormattedDate(text);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Display name"),
|
||||||
|
dataIndex: "displayName",
|
||||||
|
key: "displayName",
|
||||||
|
width: "170px",
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps("displayName"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("subscription:Duration"),
|
||||||
|
dataIndex: "duration",
|
||||||
|
key: "duration",
|
||||||
|
width: "140px",
|
||||||
|
...this.getColumnSearchProps("duration"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("subscription:Sub plane"),
|
||||||
|
dataIndex: "plan",
|
||||||
|
key: "plan",
|
||||||
|
width: "140px",
|
||||||
|
...this.getColumnSearchProps("plan"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("subscription:Sub user"),
|
||||||
|
dataIndex: "user",
|
||||||
|
key: "user",
|
||||||
|
width: "140px",
|
||||||
|
...this.getColumnSearchProps("user"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:State"),
|
||||||
|
dataIndex: "state",
|
||||||
|
key: "state",
|
||||||
|
width: "120px",
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps("state"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Action"),
|
||||||
|
dataIndex: "",
|
||||||
|
key: "op",
|
||||||
|
width: "230px",
|
||||||
|
fixed: (Setting.isMobile()) ? "false" : "right",
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/subscription/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||||
|
<PopconfirmModal
|
||||||
|
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
||||||
|
onConfirm={() => this.deleteSubscription(index)}
|
||||||
|
>
|
||||||
|
</PopconfirmModal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const paginationProps = {
|
||||||
|
total: this.state.pagination.total,
|
||||||
|
showQuickJumper: true,
|
||||||
|
showSizeChanger: true,
|
||||||
|
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Table scroll={{x: "max-content"}} columns={columns} dataSource={subscriptions} rowKey="name" size="middle" bordered pagination={paginationProps}
|
||||||
|
title={() => (
|
||||||
|
<div>
|
||||||
|
{i18next.t("general:Subscriptions")}
|
||||||
|
<Button type="primary" size="small" onClick={this.addSubscription.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
loading={this.state.loading}
|
||||||
|
onChange={this.handleTableChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch = (params = {}) => {
|
||||||
|
let field = params.searchedColumn, value = params.searchText;
|
||||||
|
const sortField = params.sortField, sortOrder = params.sortOrder;
|
||||||
|
if (params.type !== undefined && params.type !== null) {
|
||||||
|
field = "type";
|
||||||
|
value = params.type;
|
||||||
|
}
|
||||||
|
this.setState({loading: true});
|
||||||
|
SubscriptionBackend.getSubscriptions("", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
data: res.data,
|
||||||
|
pagination: {
|
||||||
|
...params.pagination,
|
||||||
|
total: res.data2,
|
||||||
|
},
|
||||||
|
searchText: params.searchText,
|
||||||
|
searchedColumn: params.searchedColumn,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (Setting.isResponseDenied(res)) {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
isAuthorized: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SubscriptionListPage;
|
@ -28,6 +28,20 @@ require("codemirror/mode/javascript/javascript");
|
|||||||
|
|
||||||
const {Option} = Select;
|
const {Option} = Select;
|
||||||
|
|
||||||
|
const applicationTemplate = {
|
||||||
|
owner: "admin", // this.props.account.applicationName,
|
||||||
|
name: "application_123",
|
||||||
|
organization: "built-in",
|
||||||
|
createdTime: "2022-01-01T01:03:42+08:00",
|
||||||
|
displayName: "New Application - 123",
|
||||||
|
logo: `${Setting.StaticBaseUrl}/img/casdoor-logo_1185x256.png`,
|
||||||
|
enablePassword: true,
|
||||||
|
enableSignUp: true,
|
||||||
|
enableSigninSession: false,
|
||||||
|
enableCodeSignin: false,
|
||||||
|
enableSamlCompress: false,
|
||||||
|
};
|
||||||
|
|
||||||
const previewTemplate = {
|
const previewTemplate = {
|
||||||
"id": 9078,
|
"id": 9078,
|
||||||
"owner": "built-in",
|
"owner": "built-in",
|
||||||
@ -37,9 +51,10 @@ const previewTemplate = {
|
|||||||
"clientIp": "159.89.126.192",
|
"clientIp": "159.89.126.192",
|
||||||
"user": "admin",
|
"user": "admin",
|
||||||
"method": "POST",
|
"method": "POST",
|
||||||
"requestUri": "/api/login",
|
"requestUri": "/api/add-application",
|
||||||
"action": "login",
|
"action": "login",
|
||||||
"isTriggered": false,
|
"isTriggered": false,
|
||||||
|
"object": JSON.stringify(applicationTemplate),
|
||||||
};
|
};
|
||||||
|
|
||||||
const userTemplate = {
|
const userTemplate = {
|
||||||
@ -49,7 +64,7 @@ const userTemplate = {
|
|||||||
"updatedTime": "",
|
"updatedTime": "",
|
||||||
"id": "9eb20f79-3bb5-4e74-99ac-39e3b9a171e8",
|
"id": "9eb20f79-3bb5-4e74-99ac-39e3b9a171e8",
|
||||||
"type": "normal-user",
|
"type": "normal-user",
|
||||||
"password": "123",
|
"password": "***",
|
||||||
"passwordSalt": "",
|
"passwordSalt": "",
|
||||||
"displayName": "Admin",
|
"displayName": "Admin",
|
||||||
"avatar": "https://cdn.casbin.com/usercontent/admin/avatar/1596241359.png",
|
"avatar": "https://cdn.casbin.com/usercontent/admin/avatar/1596241359.png",
|
||||||
@ -244,7 +259,7 @@ class WebhookEditPage extends React.Component {
|
|||||||
}} >
|
}} >
|
||||||
{
|
{
|
||||||
(
|
(
|
||||||
["signup", "login", "logout", "add-user", "update-user", "delete-user", "add-organization", "update-organization", "delete-organization", "add-application", "update-application", "delete-application", "add-provider", "update-provider", "delete-provider"].map((option, index) => {
|
["signup", "login", "logout", "add-user", "update-user", "delete-user", "add-organization", "update-organization", "delete-organization", "add-application", "update-application", "delete-application", "add-provider", "update-provider", "delete-provider", "update-subscription"].map((option, index) => {
|
||||||
return (
|
return (
|
||||||
<Option key={option} value={option}>{option}</Option>
|
<Option key={option} value={option}>{option}</Option>
|
||||||
);
|
);
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
import {authConfig} from "./Auth";
|
import {authConfig} from "./Auth";
|
||||||
import * as Setting from "../Setting";
|
import * as Setting from "../Setting";
|
||||||
|
|
||||||
export function getAccount(query) {
|
export function getAccount(query = "") {
|
||||||
return fetch(`${authConfig.serverUrl}/api/get-account${query}`, {
|
return fetch(`${authConfig.serverUrl}/api/get-account${query}`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
|
@ -14,8 +14,9 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Button, Checkbox, Col, Form, Input, Result, Row, Spin, Tabs} from "antd";
|
import {Button, Checkbox, Col, Form, Input, Result, Row, Spin, Tabs} from "antd";
|
||||||
import {LockOutlined, UserOutlined} from "@ant-design/icons";
|
import {ArrowLeftOutlined, LockOutlined, UserOutlined} from "@ant-design/icons";
|
||||||
import * as UserWebauthnBackend from "../backend/UserWebauthnBackend";
|
import * as UserWebauthnBackend from "../backend/UserWebauthnBackend";
|
||||||
|
import OrganizationSelect from "../common/select/OrganizationSelect";
|
||||||
import * as Conf from "../Conf";
|
import * as Conf from "../Conf";
|
||||||
import * as AuthBackend from "./AuthBackend";
|
import * as AuthBackend from "./AuthBackend";
|
||||||
import * as OrganizationBackend from "../backend/OrganizationBackend";
|
import * as OrganizationBackend from "../backend/OrganizationBackend";
|
||||||
@ -33,7 +34,7 @@ import LanguageSelect from "../common/select/LanguageSelect";
|
|||||||
import {CaptchaModal} from "../common/modal/CaptchaModal";
|
import {CaptchaModal} from "../common/modal/CaptchaModal";
|
||||||
import {CaptchaRule} from "../common/modal/CaptchaModal";
|
import {CaptchaRule} from "../common/modal/CaptchaModal";
|
||||||
import RedirectForm from "../common/RedirectForm";
|
import RedirectForm from "../common/RedirectForm";
|
||||||
import {MfaAuthVerifyForm, NextMfa} from "./MfaAuthVerifyForm";
|
import {MfaAuthVerifyForm, NextMfa, RequiredMfa} from "./MfaAuthVerifyForm";
|
||||||
|
|
||||||
class LoginPage extends React.Component {
|
class LoginPage extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@ -57,6 +58,7 @@ class LoginPage extends React.Component {
|
|||||||
redirectUrl: "",
|
redirectUrl: "",
|
||||||
isTermsOfUseVisible: false,
|
isTermsOfUseVisible: false,
|
||||||
termsOfUseContent: "",
|
termsOfUseContent: "",
|
||||||
|
orgChoiceMode: new URLSearchParams(props.location?.search).get("orgChoiceMode") ?? null,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.state.type === "cas" && props.match?.params.casApplicationName !== undefined) {
|
if (this.state.type === "cas" && props.match?.params.casApplicationName !== undefined) {
|
||||||
@ -224,23 +226,27 @@ class LoginPage extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
postCodeLoginAction(res) {
|
postCodeLoginAction(resp) {
|
||||||
const application = this.getApplicationObj();
|
const application = this.getApplicationObj();
|
||||||
const ths = this;
|
const ths = this;
|
||||||
const oAuthParams = Util.getOAuthGetParameters();
|
const oAuthParams = Util.getOAuthGetParameters();
|
||||||
const code = res.data;
|
const code = resp.data;
|
||||||
const concatChar = oAuthParams?.redirectUri?.includes("?") ? "&" : "?";
|
const concatChar = oAuthParams?.redirectUri?.includes("?") ? "&" : "?";
|
||||||
const noRedirect = oAuthParams.noRedirect;
|
const noRedirect = oAuthParams.noRedirect;
|
||||||
if (Setting.hasPromptPage(application)) {
|
|
||||||
AuthBackend.getAccount("")
|
|
||||||
.then((res) => {
|
|
||||||
let account = null;
|
|
||||||
if (res.status === "ok") {
|
|
||||||
account = res.data;
|
|
||||||
account.organization = res.data2;
|
|
||||||
|
|
||||||
|
if (Setting.hasPromptPage(application) || resp.msg === RequiredMfa) {
|
||||||
|
AuthBackend.getAccount()
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
const account = res.data;
|
||||||
|
account.organization = res.data2;
|
||||||
this.onUpdateAccount(account);
|
this.onUpdateAccount(account);
|
||||||
|
|
||||||
|
if (resp.msg === RequiredMfa) {
|
||||||
|
Setting.goToLink(`/prompt/${application.name}?redirectUri=${oAuthParams.redirectUri}&code=${code}&state=${oAuthParams.state}&promptType=mfa`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (Setting.isPromptAnswered(account, application)) {
|
if (Setting.isPromptAnswered(account, application)) {
|
||||||
Setting.goToLink(`${oAuthParams.redirectUri}${concatChar}code=${code}&state=${oAuthParams.state}`);
|
Setting.goToLink(`${oAuthParams.redirectUri}${concatChar}code=${code}&state=${oAuthParams.state}`);
|
||||||
} else {
|
} else {
|
||||||
@ -328,10 +334,20 @@ class LoginPage extends React.Component {
|
|||||||
const responseType = values["type"];
|
const responseType = values["type"];
|
||||||
|
|
||||||
if (responseType === "login") {
|
if (responseType === "login") {
|
||||||
Setting.showMessage("success", i18next.t("application:Logged in successfully"));
|
if (res.msg === RequiredMfa) {
|
||||||
|
AuthBackend.getAccount().then((res) => {
|
||||||
const link = Setting.getFromLink();
|
if (res.status === "ok") {
|
||||||
Setting.goToLink(link);
|
const account = res.data;
|
||||||
|
account.organization = res.data2;
|
||||||
|
this.onUpdateAccount(account);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Setting.goToLink(`/prompt/${this.getApplicationObj().name}?promptType=mfa`);
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("success", i18next.t("application:Logged in successfully"));
|
||||||
|
const link = Setting.getFromLink();
|
||||||
|
Setting.goToLink(link);
|
||||||
|
}
|
||||||
} else if (responseType === "code") {
|
} else if (responseType === "code") {
|
||||||
this.postCodeLoginAction(res);
|
this.postCodeLoginAction(res);
|
||||||
} else if (responseType === "token" || responseType === "id_token") {
|
} else if (responseType === "token" || responseType === "id_token") {
|
||||||
@ -352,6 +368,7 @@ class LoginPage extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
callback(res);
|
callback(res);
|
||||||
} else if (res.status === NextMfa) {
|
} else if (res.status === NextMfa) {
|
||||||
@ -423,6 +440,7 @@ class LoginPage extends React.Component {
|
|||||||
<Form
|
<Form
|
||||||
name="normal_login"
|
name="normal_login"
|
||||||
initialValues={{
|
initialValues={{
|
||||||
|
|
||||||
organization: application.organization,
|
organization: application.organization,
|
||||||
application: application.name,
|
application: application.name,
|
||||||
autoSignin: true,
|
autoSignin: true,
|
||||||
@ -559,7 +577,7 @@ class LoginPage extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
{
|
{
|
||||||
application.providers.filter(providerItem => this.isProviderVisible(providerItem)).map(providerItem => {
|
application.providers?.filter(providerItem => this.isProviderVisible(providerItem)).map(providerItem => {
|
||||||
return ProviderButton.renderProviderLogo(providerItem.provider, application, 40, 10, "big", this.props.location);
|
return ProviderButton.renderProviderLogo(providerItem.provider, application, 40, 10, "big", this.props.location);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -799,6 +817,102 @@ class LoginPage extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderLoginPanel(application) {
|
||||||
|
const orgChoiceMode = application.orgChoiceMode;
|
||||||
|
|
||||||
|
if (this.isOrganizationChoiceBoxVisible(orgChoiceMode)) {
|
||||||
|
return this.renderOrganizationChoiceBox(orgChoiceMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.getVerifyTotp !== undefined) {
|
||||||
|
return this.state.getVerifyTotp();
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{this.renderSignedInBox()}
|
||||||
|
{this.renderForm(application)}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderOrganizationChoiceBox(orgChoiceMode) {
|
||||||
|
const renderChoiceBox = () => {
|
||||||
|
switch (orgChoiceMode) {
|
||||||
|
case "None":
|
||||||
|
return null;
|
||||||
|
case "Select":
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<p style={{fontSize: "large"}}>
|
||||||
|
{i18next.t("login:Please select an organization to sign in")}
|
||||||
|
</p>
|
||||||
|
<OrganizationSelect style={{width: "70%"}}
|
||||||
|
onSelect={(value) => {
|
||||||
|
Setting.goToLink(`/login/${value}?orgChoiceMode=None`);
|
||||||
|
}} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
case "Input":
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<p style={{fontSize: "large"}}>
|
||||||
|
{i18next.t("login:Please type an organization to sign in")}
|
||||||
|
</p>
|
||||||
|
<Form
|
||||||
|
name="basic"
|
||||||
|
onFinish={(values) => {Setting.goToLink(`/login/${values.organizationName}?orgChoiceMode=None`);}}
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
name="organizationName"
|
||||||
|
rules={[{required: true, message: i18next.t("login:Please input your organization name!")}]}
|
||||||
|
>
|
||||||
|
<Input style={{width: "70%"}} onPressEnter={(e) => {
|
||||||
|
Setting.goToLink(`/login/${e.target.value}?orgChoiceMode=None`);
|
||||||
|
}} />
|
||||||
|
</Form.Item>
|
||||||
|
<Button type="primary" htmlType="submit">
|
||||||
|
{i18next.t("general:Confirm")}
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{height: 300, width: 300}}>
|
||||||
|
{renderChoiceBox()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
isOrganizationChoiceBoxVisible(orgChoiceMode) {
|
||||||
|
if (this.state.orgChoiceMode === "None") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = this.props.match?.path;
|
||||||
|
if (path === "/login" || path === "/login/:owner") {
|
||||||
|
return orgChoiceMode === "Select" || orgChoiceMode === "Input";
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderBackButton() {
|
||||||
|
if (this.state.orgChoiceMode === "None") {
|
||||||
|
return (
|
||||||
|
<Button type="text" size="large" icon={<ArrowLeftOutlined />}
|
||||||
|
style={{top: "65px", left: "15px", position: "absolute"}}
|
||||||
|
onClick={() => history.back()}>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const application = this.getApplicationObj();
|
const application = this.getApplicationObj();
|
||||||
if (application === undefined) {
|
if (application === undefined) {
|
||||||
@ -818,7 +932,7 @@ class LoginPage extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const visibleOAuthProviderItems = application.providers.filter(providerItem => this.isProviderVisible(providerItem));
|
const visibleOAuthProviderItems = (application.providers === null) ? [] : application.providers.filter(providerItem => this.isProviderVisible(providerItem));
|
||||||
if (this.props.preview !== "auto" && !application.enablePassword && visibleOAuthProviderItems.length === 1) {
|
if (this.props.preview !== "auto" && !application.enablePassword && visibleOAuthProviderItems.length === 1) {
|
||||||
Setting.goToLink(Provider.getAuthUrl(application, visibleOAuthProviderItems[0].provider, "signup"));
|
Setting.goToLink(Provider.getAuthUrl(application, visibleOAuthProviderItems[0].provider, "signup"));
|
||||||
return (
|
return (
|
||||||
@ -847,10 +961,13 @@ class LoginPage extends React.Component {
|
|||||||
{
|
{
|
||||||
Setting.renderLogo(application)
|
Setting.renderLogo(application)
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
this.renderBackButton()
|
||||||
|
}
|
||||||
<LanguageSelect languages={application.organizationObj.languages} style={{top: "55px", right: "5px", position: "absolute"}} />
|
<LanguageSelect languages={application.organizationObj.languages} style={{top: "55px", right: "5px", position: "absolute"}} />
|
||||||
{this.state.getVerifyTotp !== undefined ? null : this.renderSignedInBox()}
|
{
|
||||||
{this.state.getVerifyTotp !== undefined ? null : this.renderForm(application)}
|
this.renderLoginPanel(application)
|
||||||
{this.state.getVerifyTotp !== undefined ? this.state.getVerifyTotp() : null}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -20,6 +20,7 @@ import {SmsMfaType} from "./MfaSetupPage";
|
|||||||
import {MfaSmsVerifyForm} from "./MfaVerifyForm";
|
import {MfaSmsVerifyForm} from "./MfaVerifyForm";
|
||||||
|
|
||||||
export const NextMfa = "NextMfa";
|
export const NextMfa = "NextMfa";
|
||||||
|
export const RequiredMfa = "RequiredMfa";
|
||||||
|
|
||||||
export function MfaAuthVerifyForm({formValues, oAuthParams, mfaProps, application, onSuccess, onFail}) {
|
export function MfaAuthVerifyForm({formValues, oAuthParams, mfaProps, application, onSuccess, onFail}) {
|
||||||
formValues.password = "";
|
formValues.password = "";
|
||||||
|
@ -78,6 +78,7 @@ function CheckPasswordForm({user, onSuccess, onFail}) {
|
|||||||
|
|
||||||
export function MfaVerifyForm({mfaProps, application, user, onSuccess, onFail}) {
|
export function MfaVerifyForm({mfaProps, application, user, onSuccess, onFail}) {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
mfaProps = mfaProps ?? {type: ""};
|
||||||
|
|
||||||
const onFinish = ({passcode}) => {
|
const onFinish = ({passcode}) => {
|
||||||
const data = {passcode, type: mfaProps.type, ...user};
|
const data = {passcode, type: mfaProps.type, ...user};
|
||||||
@ -145,7 +146,11 @@ class MfaSetupPage extends React.Component {
|
|||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
account: props.account,
|
account: props.account,
|
||||||
current: 0,
|
applicationName: (props.applicationName ?? props.account?.signupApplication) ?? "",
|
||||||
|
isAuthenticated: props.isAuthenticated ?? false,
|
||||||
|
isPromptPage: props.isPromptPage,
|
||||||
|
redirectUri: props.redirectUri,
|
||||||
|
current: props.current ?? 0,
|
||||||
type: props.type ?? SmsMfaType,
|
type: props.type ?? SmsMfaType,
|
||||||
mfaProps: null,
|
mfaProps: null,
|
||||||
};
|
};
|
||||||
@ -155,8 +160,25 @@ class MfaSetupPage extends React.Component {
|
|||||||
this.getApplication();
|
this.getApplication();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||||
|
if (this.state.isAuthenticated === true && this.state.mfaProps === null) {
|
||||||
|
MfaBackend.MfaSetupInitiate({
|
||||||
|
type: this.state.type,
|
||||||
|
...this.getUser(),
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
this.setState({
|
||||||
|
mfaProps: res.data,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", i18next.t("mfa:Failed to initiate MFA"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getApplication() {
|
getApplication() {
|
||||||
ApplicationBackend.getApplication("admin", this.state.account.signupApplication)
|
ApplicationBackend.getApplication("admin", this.state.applicationName)
|
||||||
.then((application) => {
|
.then((application) => {
|
||||||
if (application !== null) {
|
if (application !== null) {
|
||||||
this.setState({
|
this.setState({
|
||||||
@ -181,18 +203,9 @@ class MfaSetupPage extends React.Component {
|
|||||||
return <CheckPasswordForm
|
return <CheckPasswordForm
|
||||||
user={this.getUser()}
|
user={this.getUser()}
|
||||||
onSuccess={() => {
|
onSuccess={() => {
|
||||||
MfaBackend.MfaSetupInitiate({
|
this.setState({
|
||||||
type: this.state.type,
|
current: this.state.current + 1,
|
||||||
...this.getUser(),
|
isAuthenticated: true,
|
||||||
}).then((res) => {
|
|
||||||
if (res.status === "ok") {
|
|
||||||
this.setState({
|
|
||||||
current: this.state.current + 1,
|
|
||||||
mfaProps: res.data,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
Setting.showMessage("error", i18next.t("mfa:Failed to initiate MFA"));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
onFail={(res) => {
|
onFail={(res) => {
|
||||||
@ -200,8 +213,12 @@ class MfaSetupPage extends React.Component {
|
|||||||
}}
|
}}
|
||||||
/>;
|
/>;
|
||||||
case 1:
|
case 1:
|
||||||
|
if (!this.state.isAuthenticated) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return <MfaVerifyForm
|
return <MfaVerifyForm
|
||||||
mfaProps={{...this.state.mfaProps}}
|
mfaProps={this.state.mfaProps}
|
||||||
application={this.state.application}
|
application={this.state.application}
|
||||||
user={this.getUser()}
|
user={this.getUser()}
|
||||||
onSuccess={() => {
|
onSuccess={() => {
|
||||||
@ -214,10 +231,18 @@ class MfaSetupPage extends React.Component {
|
|||||||
}}
|
}}
|
||||||
/>;
|
/>;
|
||||||
case 2:
|
case 2:
|
||||||
|
if (!this.state.isAuthenticated) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return <EnableMfaForm user={this.getUser()} mfaProps={{type: this.state.type, ...this.state.mfaProps}}
|
return <EnableMfaForm user={this.getUser()} mfaProps={{type: this.state.type, ...this.state.mfaProps}}
|
||||||
onSuccess={() => {
|
onSuccess={() => {
|
||||||
Setting.showMessage("success", i18next.t("general:Enabled successfully"));
|
Setting.showMessage("success", i18next.t("general:Enabled successfully"));
|
||||||
Setting.goToLinkSoft(this, "/account");
|
if (this.state.isPromptPage && this.state.redirectUri) {
|
||||||
|
Setting.goToLink(this.state.redirectUri);
|
||||||
|
} else {
|
||||||
|
Setting.goToLink("/account");
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
onFail={(res) => {
|
onFail={(res) => {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to enable")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to enable")}: ${res.msg}`);
|
||||||
@ -265,7 +290,9 @@ class MfaSetupPage extends React.Component {
|
|||||||
</Row>
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={24} style={{display: "flex", justifyContent: "center"}}>
|
<Col span={24} style={{display: "flex", justifyContent: "center"}}>
|
||||||
<div style={{marginTop: "10px", textAlign: "center"}}>{this.renderStep()}</div>
|
<div style={{marginTop: "10px", textAlign: "center"}}>
|
||||||
|
{this.renderStep()}
|
||||||
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
);
|
);
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user