mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-08 09:01:00 +08:00
Compare commits
122 Commits
revert-310
...
v1.767.0
Author | SHA1 | Date | |
---|---|---|---|
bf55f94d41 | |||
f14711d315 | |||
58e1c28f7c | |||
922b19c64b | |||
1d21c3fa90 | |||
6175fd6764 | |||
2ceb54f058 | |||
aaeaa7fefa | |||
d522247552 | |||
79dbdab6c9 | |||
fe40910e3b | |||
2d1736f13a | |||
12b4d1c7cd | |||
a45d2b87c1 | |||
8484465d09 | |||
dff65eee20 | |||
596016456c | |||
673261c258 | |||
3c5985a3c0 | |||
4f3d62520a | |||
96f8b3d937 | |||
7ab5a5ade1 | |||
5cbd0a96ca | |||
7ccd8c4d4f | |||
b0fa3fc484 | |||
af01c4226a | |||
7a3d85a29a | |||
fd5ccd8d41 | |||
a439c5195d | |||
ba2e997d54 | |||
0818de85d1 | |||
457c6098a4 | |||
60f979fbb5 | |||
ff53e44fa6 | |||
1832de47db | |||
535eb0c465 | |||
c190634cf3 | |||
f7559aa040 | |||
1e0b709c73 | |||
c0800b7fb3 | |||
6fcdad2100 | |||
69d26d5c21 | |||
94e6b5ecb8 | |||
95e8bdcd36 | |||
6f1f93725e | |||
7ae067e369 | |||
dde936e935 | |||
fb561a98c8 | |||
7cd8f030ee | |||
a3f8ded10c | |||
e3d135bc6e | |||
fc864b0de4 | |||
3211bcc777 | |||
9f4430ed04 | |||
05830b9ff6 | |||
347b25676f | |||
2417ff84e6 | |||
468631e654 | |||
e1dea9f697 | |||
c0f22bae43 | |||
c9635d9e2b | |||
3bd52172ea | |||
bf730050d5 | |||
5b733b7f15 | |||
034f28def9 | |||
c86ac8e6ad | |||
d647eed22a | |||
717c53f6e5 | |||
097adac871 | |||
74543b9533 | |||
110dc04179 | |||
6464bd10dc | |||
db878a890e | |||
12d6d8e6ce | |||
8ed6e4f934 | |||
ed9732caf9 | |||
0de4e7da38 | |||
a330fbc11f | |||
ed158d4981 | |||
8df965b98d | |||
2c3749820e | |||
0b17cb9746 | |||
e2ce9ad625 | |||
64491abc64 | |||
934a8947c8 | |||
943edfb48b | |||
0d02b5e768 | |||
ba8d0b5f46 | |||
973a1df6c2 | |||
05bfd3a3a3 | |||
69aa3c8a8b | |||
a1b010a406 | |||
89e92cbd47 | |||
d4c8193357 | |||
9b33800b4c | |||
ec98785172 | |||
45dd4cc344 | |||
1adb172d6b | |||
c08f2b1f3f | |||
62bb257c6d | |||
230a77e3e3 | |||
dce0a96dea | |||
65563fa0cd | |||
f2a94f671a | |||
1460a0498f | |||
adc63ea726 | |||
0b8be016c5 | |||
986dcbbda1 | |||
7d3920fb1f | |||
b794ef87ee | |||
a0d6f2125e | |||
85cbb7d074 | |||
fdc1be9452 | |||
2bd7dabd33 | |||
9b9a58e7ac | |||
38e389e8c8 | |||
ab5fcf848e | |||
b4e51b4631 | |||
45e25acc80 | |||
97dcf24a91 | |||
4c0fff66ff | |||
e7230700e0 |
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@ -114,12 +114,12 @@ jobs:
|
|||||||
wait-on-timeout: 210
|
wait-on-timeout: 210
|
||||||
working-directory: ./web
|
working-directory: ./web
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v4
|
||||||
if: failure()
|
if: failure()
|
||||||
with:
|
with:
|
||||||
name: cypress-screenshots
|
name: cypress-screenshots
|
||||||
path: ./web/cypress/screenshots
|
path: ./web/cypress/screenshots
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v4
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: cypress-videos
|
name: cypress-videos
|
||||||
@ -147,7 +147,7 @@ jobs:
|
|||||||
- name: Release
|
- name: Release
|
||||||
run: yarn global add semantic-release@17.4.4 && semantic-release
|
run: yarn global add semantic-release@17.4.4 && semantic-release
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Fetch Current version
|
- name: Fetch Current version
|
||||||
id: get-current-tag
|
id: get-current-tag
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
<a href="https://github.com/casdoor/casdoor/releases/latest">
|
<a href="https://github.com/casdoor/casdoor/releases/latest">
|
||||||
<img alt="GitHub Release" src="https://img.shields.io/github/v/release/casdoor/casdoor.svg">
|
<img alt="GitHub Release" src="https://img.shields.io/github/v/release/casdoor/casdoor.svg">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://hub.docker.com/repository/docker/casbin/casdoor">
|
<a href="https://hub.docker.com/r/casbin/casdoor">
|
||||||
<img alt="Docker Image Version (latest semver)" src="https://img.shields.io/badge/Docker%20Hub-latest-brightgreen">
|
<img alt="Docker Image Version (latest semver)" src="https://img.shields.io/badge/Docker%20Hub-latest-brightgreen">
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
@ -77,6 +77,7 @@ p, *, *, POST, /api/verify-code, *, *
|
|||||||
p, *, *, POST, /api/reset-email-or-phone, *, *
|
p, *, *, POST, /api/reset-email-or-phone, *, *
|
||||||
p, *, *, POST, /api/upload-resource, *, *
|
p, *, *, POST, /api/upload-resource, *, *
|
||||||
p, *, *, GET, /.well-known/openid-configuration, *, *
|
p, *, *, GET, /.well-known/openid-configuration, *, *
|
||||||
|
p, *, *, GET, /.well-known/webfinger, *, *
|
||||||
p, *, *, *, /.well-known/jwks, *, *
|
p, *, *, *, /.well-known/jwks, *, *
|
||||||
p, *, *, GET, /api/get-saml-login, *, *
|
p, *, *, GET, /api/get-saml-login, *, *
|
||||||
p, *, *, POST, /api/acs, *, *
|
p, *, *, POST, /api/acs, *, *
|
||||||
@ -97,6 +98,7 @@ p, *, *, GET, /api/get-organization-names, *, *
|
|||||||
p, *, *, GET, /api/get-all-objects, *, *
|
p, *, *, GET, /api/get-all-objects, *, *
|
||||||
p, *, *, GET, /api/get-all-actions, *, *
|
p, *, *, GET, /api/get-all-actions, *, *
|
||||||
p, *, *, GET, /api/get-all-roles, *, *
|
p, *, *, GET, /api/get-all-roles, *, *
|
||||||
|
p, *, *, GET, /api/run-casbin-command, *, *
|
||||||
p, *, *, GET, /api/get-invitation-info, *, *
|
p, *, *, GET, /api/get-invitation-info, *, *
|
||||||
p, *, *, GET, /api/faceid-signin-begin, *, *
|
p, *, *, GET, /api/faceid-signin-begin, *, *
|
||||||
`
|
`
|
||||||
|
@ -26,6 +26,10 @@ func GetCaptchaProvider(captchaType string) CaptchaProvider {
|
|||||||
return NewDefaultCaptchaProvider()
|
return NewDefaultCaptchaProvider()
|
||||||
case "reCAPTCHA":
|
case "reCAPTCHA":
|
||||||
return NewReCaptchaProvider()
|
return NewReCaptchaProvider()
|
||||||
|
case "reCAPTCHA v2":
|
||||||
|
return NewReCaptchaProvider()
|
||||||
|
case "reCAPTCHA v3":
|
||||||
|
return NewReCaptchaProvider()
|
||||||
case "Aliyun Captcha":
|
case "Aliyun Captcha":
|
||||||
return NewAliyunCaptchaProvider()
|
return NewAliyunCaptchaProvider()
|
||||||
case "hCaptcha":
|
case "hCaptcha":
|
||||||
|
@ -21,11 +21,16 @@ originFrontend =
|
|||||||
staticBaseUrl = "https://cdn.casbin.org"
|
staticBaseUrl = "https://cdn.casbin.org"
|
||||||
isDemoMode = false
|
isDemoMode = false
|
||||||
batchSize = 100
|
batchSize = 100
|
||||||
|
enableErrorMask = false
|
||||||
enableGzip = true
|
enableGzip = true
|
||||||
|
inactiveTimeoutMinutes =
|
||||||
ldapServerPort = 389
|
ldapServerPort = 389
|
||||||
|
ldapsCertId = ""
|
||||||
|
ldapsServerPort = 636
|
||||||
radiusServerPort = 1812
|
radiusServerPort = 1812
|
||||||
radiusSecret = "secret"
|
radiusSecret = "secret"
|
||||||
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}
|
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}
|
||||||
logConfig = {"filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"}
|
logConfig = {"filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"}
|
||||||
|
initDataNewOnly = false
|
||||||
initDataFile = "./init_data.json"
|
initDataFile = "./init_data.json"
|
||||||
frontendBaseDir = "../casdoor"
|
frontendBaseDir = "../cc_0"
|
@ -116,6 +116,13 @@ func (c *ApiController) Signup() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
|
||||||
|
err = object.CheckEntryIp(clientIp, nil, application, organization, c.GetAcceptLanguage())
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
||||||
@ -200,6 +207,10 @@ func (c *ApiController) Signup() {
|
|||||||
Type: userType,
|
Type: userType,
|
||||||
Password: authForm.Password,
|
Password: authForm.Password,
|
||||||
DisplayName: authForm.Name,
|
DisplayName: authForm.Name,
|
||||||
|
Gender: authForm.Gender,
|
||||||
|
Bio: authForm.Bio,
|
||||||
|
Tag: authForm.Tag,
|
||||||
|
Education: authForm.Education,
|
||||||
Avatar: organization.DefaultAvatar,
|
Avatar: organization.DefaultAvatar,
|
||||||
Email: authForm.Email,
|
Email: authForm.Email,
|
||||||
Phone: authForm.Phone,
|
Phone: authForm.Phone,
|
||||||
@ -234,6 +245,10 @@ func (c *ApiController) Signup() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if invitation != nil && invitation.SignupGroup != "" {
|
||||||
|
user.Groups = []string{invitation.SignupGroup}
|
||||||
|
}
|
||||||
|
|
||||||
affected, err := object.AddUser(user)
|
affected, err := object.AddUser(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
|
@ -110,6 +110,9 @@ func (c *ApiController) GetApplication() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
|
||||||
|
object.CheckEntryIp(clientIp, nil, application, nil, c.GetAcceptLanguage())
|
||||||
|
|
||||||
c.ResponseOk(object.GetMaskedApplication(application, userId))
|
c.ResponseOk(object.GetMaskedApplication(application, userId))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,6 +232,11 @@ func (c *ApiController) UpdateApplication() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = object.CheckIpWhitelist(application.IpWhitelist, c.GetAcceptLanguage()); err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.Data["json"] = wrapActionResponse(object.UpdateApplication(id, &application))
|
c.Data["json"] = wrapActionResponse(object.UpdateApplication(id, &application))
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
@ -259,6 +267,11 @@ func (c *ApiController) AddApplication() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = object.CheckIpWhitelist(application.IpWhitelist, c.GetAcceptLanguage()); err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.Data["json"] = wrapActionResponse(object.AddApplication(&application))
|
c.Data["json"] = wrapActionResponse(object.AddApplication(&application))
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
@ -55,6 +55,13 @@ func tokenToResponse(token *object.Token) *Response {
|
|||||||
func (c *ApiController) HandleLoggedIn(application *object.Application, user *object.User, form *form.AuthForm) (resp *Response) {
|
func (c *ApiController) HandleLoggedIn(application *object.Application, user *object.User, form *form.AuthForm) (resp *Response) {
|
||||||
userId := user.GetId()
|
userId := user.GetId()
|
||||||
|
|
||||||
|
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
|
||||||
|
err := object.CheckEntryIp(clientIp, user, application, application.OrganizationObj, c.GetAcceptLanguage())
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
allowed, err := object.CheckLoginPermission(userId, application)
|
allowed, err := object.CheckLoginPermission(userId, application)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error(), nil)
|
c.ResponseError(err.Error(), nil)
|
||||||
@ -256,6 +263,9 @@ func (c *ApiController) GetApplicationLogin() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
|
||||||
|
object.CheckEntryIp(clientIp, nil, application, nil, c.GetAcceptLanguage())
|
||||||
|
|
||||||
application = object.GetMaskedApplication(application, "")
|
application = object.GetMaskedApplication(application, "")
|
||||||
if msg != "" {
|
if msg != "" {
|
||||||
c.ResponseError(msg, application)
|
c.ResponseError(msg, application)
|
||||||
@ -463,6 +473,15 @@ func (c *ApiController) Login() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
password := authForm.Password
|
password := authForm.Password
|
||||||
|
|
||||||
|
if application.OrganizationObj != nil {
|
||||||
|
password, err = util.GetUnobfuscatedPassword(application.OrganizationObj.PasswordObfuscatorType, application.OrganizationObj.PasswordObfuscatorKey, authForm.Password)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
isSigninViaLdap := authForm.SigninMethod == "LDAP"
|
isSigninViaLdap := authForm.SigninMethod == "LDAP"
|
||||||
var isPasswordWithLdapEnabled bool
|
var isPasswordWithLdapEnabled bool
|
||||||
if authForm.SigninMethod == "Password" {
|
if authForm.SigninMethod == "Password" {
|
||||||
@ -835,6 +854,7 @@ func (c *ApiController) Login() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if authForm.Passcode != "" {
|
if authForm.Passcode != "" {
|
||||||
|
user.CountryCode = user.GetCountryCode(user.CountryCode)
|
||||||
mfaUtil := object.GetMfaUtil(authForm.MfaType, user.GetPreferredMfaProps(false))
|
mfaUtil := object.GetMfaUtil(authForm.MfaType, user.GetPreferredMfaProps(false))
|
||||||
if mfaUtil == nil {
|
if mfaUtil == nil {
|
||||||
c.ResponseError("Invalid multi-factor authentication type")
|
c.ResponseError("Invalid multi-factor authentication type")
|
||||||
|
114
controllers/casbin_cli_api.go
Normal file
114
controllers/casbin_cli_api.go
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
// Copyright 2024 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"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func processArgsToTempFiles(args []string) ([]string, []string, error) {
|
||||||
|
tempFiles := []string{}
|
||||||
|
newArgs := []string{}
|
||||||
|
for i := 0; i < len(args); i++ {
|
||||||
|
if (args[i] == "-m" || args[i] == "-p") && i+1 < len(args) {
|
||||||
|
pattern := fmt.Sprintf("casbin_temp_%s_*.conf", args[i])
|
||||||
|
tempFile, err := os.CreateTemp("", pattern)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to create temp file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = tempFile.WriteString(args[i+1])
|
||||||
|
if err != nil {
|
||||||
|
tempFile.Close()
|
||||||
|
return nil, nil, fmt.Errorf("failed to write to temp file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tempFile.Close()
|
||||||
|
tempFiles = append(tempFiles, tempFile.Name())
|
||||||
|
newArgs = append(newArgs, args[i], tempFile.Name())
|
||||||
|
i++
|
||||||
|
} else {
|
||||||
|
newArgs = append(newArgs, args[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tempFiles, newArgs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunCasbinCommand
|
||||||
|
// @Title RunCasbinCommand
|
||||||
|
// @Tag Enforcer API
|
||||||
|
// @Description Call Casbin CLI commands
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /run-casbin-command [get]
|
||||||
|
func (c *ApiController) RunCasbinCommand() {
|
||||||
|
language := c.Input().Get("language")
|
||||||
|
argString := c.Input().Get("args")
|
||||||
|
|
||||||
|
if language == "" {
|
||||||
|
language = "go"
|
||||||
|
}
|
||||||
|
// use "casbin-go-cli" by default, can be also "casbin-java-cli", "casbin-node-cli", etc.
|
||||||
|
// the pre-built binary of "casbin-go-cli" can be found at: https://github.com/casbin/casbin-go-cli/releases
|
||||||
|
binaryName := fmt.Sprintf("casbin-%s-cli", language)
|
||||||
|
|
||||||
|
_, err := exec.LookPath(binaryName)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(fmt.Sprintf("executable file: %s not found in PATH", binaryName))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// RBAC model & policy example:
|
||||||
|
// https://door.casdoor.com/api/run-casbin-command?language=go&args=["enforce", "-m", "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[role_definition]\ng = _, _\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = g(r.sub, p.sub) %26%26 r.obj == p.obj %26%26 r.act == p.act", "-p", "p, alice, data1, read\np, bob, data2, write\np, data2_admin, data2, read\np, data2_admin, data2, write\ng, alice, data2_admin", "alice", "data1", "read"]
|
||||||
|
// Casbin CLI usage:
|
||||||
|
// https://github.com/jcasbin/casbin-java-cli?tab=readme-ov-file#get-started
|
||||||
|
var args []string
|
||||||
|
err = json.Unmarshal([]byte(argString), &args)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tempFiles, processedArgs, err := processArgsToTempFiles(args)
|
||||||
|
defer func() {
|
||||||
|
for _, file := range tempFiles {
|
||||||
|
os.Remove(file)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
command := exec.Command(binaryName, processedArgs...)
|
||||||
|
outputBytes, err := command.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
errorString := err.Error()
|
||||||
|
if outputBytes != nil {
|
||||||
|
output := string(outputBytes)
|
||||||
|
errorString = fmt.Sprintf("%s, error: %s", output, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseError(errorString)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
output := string(outputBytes)
|
||||||
|
output = strings.TrimSuffix(output, "\n")
|
||||||
|
c.ResponseOk(output)
|
||||||
|
}
|
@ -60,7 +60,6 @@ func (c *ApiController) Unlink() {
|
|||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if application == nil {
|
if application == nil {
|
||||||
c.ResponseError(c.T("link:You can't unlink yourself, you are not a member of any application"))
|
c.ResponseError(c.T("link:You can't unlink yourself, you are not a member of any application"))
|
||||||
return
|
return
|
||||||
|
@ -22,13 +22,6 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
MfaRecoveryCodesSession = "mfa_recovery_codes"
|
|
||||||
MfaCountryCodeSession = "mfa_country_code"
|
|
||||||
MfaDestSession = "mfa_dest"
|
|
||||||
MfaTotpSecretSession = "mfa_totp_secret"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MfaSetupInitiate
|
// MfaSetupInitiate
|
||||||
// @Title MfaSetupInitiate
|
// @Title MfaSetupInitiate
|
||||||
// @Tag MFA API
|
// @Tag MFA API
|
||||||
@ -72,11 +65,6 @@ func (c *ApiController) MfaSetupInitiate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
recoveryCode := uuid.NewString()
|
recoveryCode := uuid.NewString()
|
||||||
c.SetSession(MfaRecoveryCodesSession, recoveryCode)
|
|
||||||
if mfaType == object.TotpType {
|
|
||||||
c.SetSession(MfaTotpSecretSession, mfaProps.Secret)
|
|
||||||
}
|
|
||||||
|
|
||||||
mfaProps.RecoveryCodes = []string{recoveryCode}
|
mfaProps.RecoveryCodes = []string{recoveryCode}
|
||||||
|
|
||||||
resp := mfaProps
|
resp := mfaProps
|
||||||
@ -94,6 +82,9 @@ func (c *ApiController) MfaSetupInitiate() {
|
|||||||
func (c *ApiController) MfaSetupVerify() {
|
func (c *ApiController) MfaSetupVerify() {
|
||||||
mfaType := c.Ctx.Request.Form.Get("mfaType")
|
mfaType := c.Ctx.Request.Form.Get("mfaType")
|
||||||
passcode := c.Ctx.Request.Form.Get("passcode")
|
passcode := c.Ctx.Request.Form.Get("passcode")
|
||||||
|
secret := c.Ctx.Request.Form.Get("secret")
|
||||||
|
dest := c.Ctx.Request.Form.Get("dest")
|
||||||
|
countryCode := c.Ctx.Request.Form.Get("countryCode")
|
||||||
|
|
||||||
if mfaType == "" || passcode == "" {
|
if mfaType == "" || passcode == "" {
|
||||||
c.ResponseError("missing auth type or passcode")
|
c.ResponseError("missing auth type or passcode")
|
||||||
@ -104,32 +95,28 @@ func (c *ApiController) MfaSetupVerify() {
|
|||||||
MfaType: mfaType,
|
MfaType: mfaType,
|
||||||
}
|
}
|
||||||
if mfaType == object.TotpType {
|
if mfaType == object.TotpType {
|
||||||
secret := c.GetSession(MfaTotpSecretSession)
|
if secret == "" {
|
||||||
if secret == nil {
|
|
||||||
c.ResponseError("totp secret is missing")
|
c.ResponseError("totp secret is missing")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
config.Secret = secret.(string)
|
config.Secret = secret
|
||||||
} else if mfaType == object.SmsType {
|
} else if mfaType == object.SmsType {
|
||||||
dest := c.GetSession(MfaDestSession)
|
if dest == "" {
|
||||||
if dest == nil {
|
|
||||||
c.ResponseError("destination is missing")
|
c.ResponseError("destination is missing")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
config.Secret = dest.(string)
|
config.Secret = dest
|
||||||
countryCode := c.GetSession(MfaCountryCodeSession)
|
if countryCode == "" {
|
||||||
if countryCode == nil {
|
|
||||||
c.ResponseError("country code is missing")
|
c.ResponseError("country code is missing")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
config.CountryCode = countryCode.(string)
|
config.CountryCode = countryCode
|
||||||
} else if mfaType == object.EmailType {
|
} else if mfaType == object.EmailType {
|
||||||
dest := c.GetSession(MfaDestSession)
|
if dest == "" {
|
||||||
if dest == nil {
|
|
||||||
c.ResponseError("destination is missing")
|
c.ResponseError("destination is missing")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
config.Secret = dest.(string)
|
config.Secret = dest
|
||||||
}
|
}
|
||||||
|
|
||||||
mfaUtil := object.GetMfaUtil(mfaType, config)
|
mfaUtil := object.GetMfaUtil(mfaType, config)
|
||||||
@ -159,6 +146,10 @@ func (c *ApiController) MfaSetupEnable() {
|
|||||||
owner := c.Ctx.Request.Form.Get("owner")
|
owner := c.Ctx.Request.Form.Get("owner")
|
||||||
name := c.Ctx.Request.Form.Get("name")
|
name := c.Ctx.Request.Form.Get("name")
|
||||||
mfaType := c.Ctx.Request.Form.Get("mfaType")
|
mfaType := c.Ctx.Request.Form.Get("mfaType")
|
||||||
|
secret := c.Ctx.Request.Form.Get("secret")
|
||||||
|
dest := c.Ctx.Request.Form.Get("dest")
|
||||||
|
countryCode := c.Ctx.Request.Form.Get("secret")
|
||||||
|
recoveryCodes := c.Ctx.Request.Form.Get("recoveryCodes")
|
||||||
|
|
||||||
user, err := object.GetUser(util.GetId(owner, name))
|
user, err := object.GetUser(util.GetId(owner, name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -176,43 +167,39 @@ func (c *ApiController) MfaSetupEnable() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if mfaType == object.TotpType {
|
if mfaType == object.TotpType {
|
||||||
secret := c.GetSession(MfaTotpSecretSession)
|
if secret == "" {
|
||||||
if secret == nil {
|
|
||||||
c.ResponseError("totp secret is missing")
|
c.ResponseError("totp secret is missing")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
config.Secret = secret.(string)
|
config.Secret = secret
|
||||||
} else if mfaType == object.EmailType {
|
} else if mfaType == object.EmailType {
|
||||||
if user.Email == "" {
|
if user.Email == "" {
|
||||||
dest := c.GetSession(MfaDestSession)
|
if dest == "" {
|
||||||
if dest == nil {
|
|
||||||
c.ResponseError("destination is missing")
|
c.ResponseError("destination is missing")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user.Email = dest.(string)
|
user.Email = dest
|
||||||
}
|
}
|
||||||
} else if mfaType == object.SmsType {
|
} else if mfaType == object.SmsType {
|
||||||
if user.Phone == "" {
|
if user.Phone == "" {
|
||||||
dest := c.GetSession(MfaDestSession)
|
if dest == "" {
|
||||||
if dest == nil {
|
|
||||||
c.ResponseError("destination is missing")
|
c.ResponseError("destination is missing")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user.Phone = dest.(string)
|
user.Phone = dest
|
||||||
countryCode := c.GetSession(MfaCountryCodeSession)
|
if countryCode == "" {
|
||||||
if countryCode == nil {
|
|
||||||
c.ResponseError("country code is missing")
|
c.ResponseError("country code is missing")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user.CountryCode = countryCode.(string)
|
user.CountryCode = countryCode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
recoveryCodes := c.GetSession(MfaRecoveryCodesSession)
|
|
||||||
if recoveryCodes == nil {
|
if recoveryCodes == "" {
|
||||||
c.ResponseError("recovery codes is missing")
|
c.ResponseError("recovery codes is missing")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
config.RecoveryCodes = []string{recoveryCodes.(string)}
|
config.RecoveryCodes = []string{recoveryCodes}
|
||||||
|
|
||||||
mfaUtil := object.GetMfaUtil(mfaType, config)
|
mfaUtil := object.GetMfaUtil(mfaType, config)
|
||||||
if mfaUtil == nil {
|
if mfaUtil == nil {
|
||||||
@ -226,14 +213,6 @@ func (c *ApiController) MfaSetupEnable() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.DelSession(MfaRecoveryCodesSession)
|
|
||||||
if mfaType == object.TotpType {
|
|
||||||
c.DelSession(MfaTotpSecretSession)
|
|
||||||
} else {
|
|
||||||
c.DelSession(MfaCountryCodeSession)
|
|
||||||
c.DelSession(MfaDestSession)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.ResponseOk(http.StatusText(http.StatusOK))
|
c.ResponseOk(http.StatusText(http.StatusOK))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,11 @@
|
|||||||
|
|
||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import "github.com/casdoor/casdoor/object"
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
)
|
||||||
|
|
||||||
// GetOidcDiscovery
|
// GetOidcDiscovery
|
||||||
// @Title GetOidcDiscovery
|
// @Title GetOidcDiscovery
|
||||||
@ -42,3 +46,31 @@ func (c *RootController) GetJwks() {
|
|||||||
c.Data["json"] = jwks
|
c.Data["json"] = jwks
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetWebFinger
|
||||||
|
// @Title GetWebFinger
|
||||||
|
// @Tag OIDC API
|
||||||
|
// @Param resource query string true "resource"
|
||||||
|
// @Success 200 {object} object.WebFinger
|
||||||
|
// @router /.well-known/webfinger [get]
|
||||||
|
func (c *RootController) GetWebFinger() {
|
||||||
|
resource := c.Input().Get("resource")
|
||||||
|
rels := []string{}
|
||||||
|
host := c.Ctx.Request.Host
|
||||||
|
|
||||||
|
for key, value := range c.Input() {
|
||||||
|
if strings.HasPrefix(key, "rel") {
|
||||||
|
rels = append(rels, value...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
webfinger, err := object.GetWebFinger(resource, rels, host)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = webfinger
|
||||||
|
c.Ctx.Output.ContentType("application/jrd+json")
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
@ -65,7 +65,7 @@ func (c *ApiController) GetOrganizations() {
|
|||||||
c.ResponseOk(organizations)
|
c.ResponseOk(organizations)
|
||||||
} else {
|
} else {
|
||||||
limit := util.ParseInt(limit)
|
limit := util.ParseInt(limit)
|
||||||
count, err := object.GetOrganizationCount(owner, field, value)
|
count, err := object.GetOrganizationCount(owner, organizationName, field, value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
@ -119,6 +119,11 @@ func (c *ApiController) UpdateOrganization() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = object.CheckIpWhitelist(organization.IpWhitelist, c.GetAcceptLanguage()); err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.Data["json"] = wrapActionResponse(object.UpdateOrganization(id, &organization))
|
c.Data["json"] = wrapActionResponse(object.UpdateOrganization(id, &organization))
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
@ -138,7 +143,7 @@ func (c *ApiController) AddOrganization() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
count, err := object.GetOrganizationCount("", "", "")
|
count, err := object.GetOrganizationCount("", "", "", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
@ -149,6 +154,11 @@ func (c *ApiController) AddOrganization() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = object.CheckIpWhitelist(organization.IpWhitelist, c.GetAcceptLanguage()); err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.Data["json"] = wrapActionResponse(object.AddOrganization(&organization))
|
c.Data["json"] = wrapActionResponse(object.AddOrganization(&organization))
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
@ -182,6 +182,10 @@ func (c *ApiController) BuyProduct() {
|
|||||||
paidUserName := c.Input().Get("userName")
|
paidUserName := c.Input().Get("userName")
|
||||||
owner, _ := util.GetOwnerAndNameFromId(id)
|
owner, _ := util.GetOwnerAndNameFromId(id)
|
||||||
userId := util.GetId(owner, paidUserName)
|
userId := util.GetId(owner, paidUserName)
|
||||||
|
if paidUserName != "" && !c.IsAdmin() {
|
||||||
|
c.ResponseError(c.T("general:Only admin user can specify user"))
|
||||||
|
return
|
||||||
|
}
|
||||||
if paidUserName == "" {
|
if paidUserName == "" {
|
||||||
userId = c.GetSessionUsername()
|
userId = c.GetSessionUsername()
|
||||||
}
|
}
|
||||||
|
@ -257,7 +257,7 @@ func (c *ApiController) UploadResource() {
|
|||||||
fileType, _ = util.GetOwnerAndNameFromIdNoCheck(mimeType + "/")
|
fileType, _ = util.GetOwnerAndNameFromIdNoCheck(mimeType + "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
fullFilePath = object.GetTruncatedPath(provider, fullFilePath, 175)
|
fullFilePath = object.GetTruncatedPath(provider, fullFilePath, 450)
|
||||||
if tag != "avatar" && tag != "termsOfUse" && !strings.HasPrefix(tag, "idCard") {
|
if tag != "avatar" && tag != "termsOfUse" && !strings.HasPrefix(tag, "idCard") {
|
||||||
ext := filepath.Ext(filepath.Base(fullFilePath))
|
ext := filepath.Ext(filepath.Base(fullFilePath))
|
||||||
index := len(fullFilePath) - len(ext)
|
index := len(fullFilePath) - len(ext)
|
||||||
|
@ -322,17 +322,22 @@ func (c *ApiController) IntrospectToken() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tokenTypeHint := c.Input().Get("token_type_hint")
|
tokenTypeHint := c.Input().Get("token_type_hint")
|
||||||
token, err := object.GetTokenByTokenValue(tokenValue, tokenTypeHint)
|
var token *object.Token
|
||||||
if err != nil {
|
if tokenTypeHint != "" {
|
||||||
c.ResponseTokenError(err.Error())
|
token, err = object.GetTokenByTokenValue(tokenValue, tokenTypeHint)
|
||||||
return
|
if err != nil {
|
||||||
}
|
c.ResponseTokenError(err.Error())
|
||||||
if token == nil {
|
return
|
||||||
c.Data["json"] = &object.IntrospectionResponse{Active: false}
|
}
|
||||||
c.ServeJSON()
|
if token == nil {
|
||||||
return
|
c.Data["json"] = &object.IntrospectionResponse{Active: false}
|
||||||
|
c.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var introspectionResponse object.IntrospectionResponse
|
||||||
|
|
||||||
if application.TokenFormat == "JWT-Standard" {
|
if application.TokenFormat == "JWT-Standard" {
|
||||||
jwtToken, err := object.ParseStandardJwtTokenByApplication(tokenValue, application)
|
jwtToken, err := object.ParseStandardJwtTokenByApplication(tokenValue, application)
|
||||||
if err != nil || jwtToken.Valid() != nil {
|
if err != nil || jwtToken.Valid() != nil {
|
||||||
@ -344,12 +349,37 @@ func (c *ApiController) IntrospectToken() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Data["json"] = &object.IntrospectionResponse{
|
introspectionResponse = object.IntrospectionResponse{
|
||||||
Active: true,
|
Active: true,
|
||||||
Scope: jwtToken.Scope,
|
Scope: jwtToken.Scope,
|
||||||
ClientId: clientId,
|
ClientId: clientId,
|
||||||
Username: token.User,
|
Username: jwtToken.Name,
|
||||||
TokenType: token.TokenType,
|
TokenType: jwtToken.TokenType,
|
||||||
|
Exp: jwtToken.ExpiresAt.Unix(),
|
||||||
|
Iat: jwtToken.IssuedAt.Unix(),
|
||||||
|
Nbf: jwtToken.NotBefore.Unix(),
|
||||||
|
Sub: jwtToken.Subject,
|
||||||
|
Aud: jwtToken.Audience,
|
||||||
|
Iss: jwtToken.Issuer,
|
||||||
|
Jti: jwtToken.ID,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
jwtToken, err := object.ParseJwtTokenByApplication(tokenValue, application)
|
||||||
|
if err != nil || jwtToken.Valid() != nil {
|
||||||
|
// and token revoked case. but we not implement
|
||||||
|
// TODO: 2022-03-03 add token revoked check, when we implemented the Token Revocation(rfc7009) Specs.
|
||||||
|
// refs: https://tools.ietf.org/html/rfc7009
|
||||||
|
c.Data["json"] = &object.IntrospectionResponse{Active: false}
|
||||||
|
c.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
introspectionResponse = object.IntrospectionResponse{
|
||||||
|
Active: true,
|
||||||
|
Scope: jwtToken.Scope,
|
||||||
|
ClientId: clientId,
|
||||||
|
Username: jwtToken.Name,
|
||||||
|
TokenType: jwtToken.TokenType,
|
||||||
Exp: jwtToken.ExpiresAt.Unix(),
|
Exp: jwtToken.ExpiresAt.Unix(),
|
||||||
Iat: jwtToken.IssuedAt.Unix(),
|
Iat: jwtToken.IssuedAt.Unix(),
|
||||||
Nbf: jwtToken.NotBefore.Unix(),
|
Nbf: jwtToken.NotBefore.Unix(),
|
||||||
@ -358,33 +388,22 @@ func (c *ApiController) IntrospectToken() {
|
|||||||
Iss: jwtToken.Issuer,
|
Iss: jwtToken.Issuer,
|
||||||
Jti: jwtToken.ID,
|
Jti: jwtToken.ID,
|
||||||
}
|
}
|
||||||
c.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
jwtToken, err := object.ParseJwtTokenByApplication(tokenValue, application)
|
if tokenTypeHint == "" {
|
||||||
if err != nil || jwtToken.Valid() != nil {
|
token, err = object.GetTokenByTokenValue(tokenValue, introspectionResponse.TokenType)
|
||||||
// and token revoked case. but we not implement
|
if err != nil {
|
||||||
// TODO: 2022-03-03 add token revoked check, when we implemented the Token Revocation(rfc7009) Specs.
|
c.ResponseTokenError(err.Error())
|
||||||
// refs: https://tools.ietf.org/html/rfc7009
|
return
|
||||||
c.Data["json"] = &object.IntrospectionResponse{Active: false}
|
}
|
||||||
c.ServeJSON()
|
if token == nil {
|
||||||
return
|
c.Data["json"] = &object.IntrospectionResponse{Active: false}
|
||||||
|
c.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
introspectionResponse.TokenType = token.TokenType
|
||||||
|
|
||||||
c.Data["json"] = &object.IntrospectionResponse{
|
c.Data["json"] = introspectionResponse
|
||||||
Active: true,
|
|
||||||
Scope: jwtToken.Scope,
|
|
||||||
ClientId: clientId,
|
|
||||||
Username: token.User,
|
|
||||||
TokenType: token.TokenType,
|
|
||||||
Exp: jwtToken.ExpiresAt.Unix(),
|
|
||||||
Iat: jwtToken.IssuedAt.Unix(),
|
|
||||||
Nbf: jwtToken.NotBefore.Unix(),
|
|
||||||
Sub: jwtToken.Subject,
|
|
||||||
Aud: jwtToken.Audience,
|
|
||||||
Iss: jwtToken.Issuer,
|
|
||||||
Jti: jwtToken.ID,
|
|
||||||
}
|
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
@ -289,6 +289,16 @@ func (c *ApiController) UpdateUser() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if user.MfaEmailEnabled && user.Email == "" {
|
||||||
|
c.ResponseError(c.T("user:MFA email is enabled but email is empty"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.MfaPhoneEnabled && user.Phone == "" {
|
||||||
|
c.ResponseError(c.T("user:MFA phone is enabled but phone number is empty"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if msg := object.CheckUpdateUser(oldUser, &user, c.GetAcceptLanguage()); msg != "" {
|
if msg := object.CheckUpdateUser(oldUser, &user, c.GetAcceptLanguage()); msg != "" {
|
||||||
c.ResponseError(msg)
|
c.ResponseError(msg)
|
||||||
return
|
return
|
||||||
@ -354,7 +364,8 @@ func (c *ApiController) AddUser() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := object.CheckUsername(user.Name, c.GetAcceptLanguage())
|
emptyUser := object.User{}
|
||||||
|
msg := object.CheckUpdateUser(&emptyUser, &user, c.GetAcceptLanguage())
|
||||||
if msg != "" {
|
if msg != "" {
|
||||||
c.ResponseError(msg)
|
c.ResponseError(msg)
|
||||||
return
|
return
|
||||||
@ -400,6 +411,12 @@ func (c *ApiController) GetEmailAndPhone() {
|
|||||||
organization := c.Ctx.Request.Form.Get("organization")
|
organization := c.Ctx.Request.Form.Get("organization")
|
||||||
username := c.Ctx.Request.Form.Get("username")
|
username := c.Ctx.Request.Form.Get("username")
|
||||||
|
|
||||||
|
enableErrorMask2 := conf.GetConfigBool("enableErrorMask2")
|
||||||
|
if enableErrorMask2 {
|
||||||
|
c.ResponseError("Error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
user, err := object.GetUserByFields(organization, username)
|
user, err := object.GetUserByFields(organization, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
@ -473,7 +490,12 @@ func (c *ApiController) SetPassword() {
|
|||||||
c.ResponseError(c.T("general:Missing parameter"))
|
c.ResponseError(c.T("general:Missing parameter"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if userId != c.GetSession("verifiedUserId") {
|
||||||
|
c.ResponseError(c.T("general:Wrong userId"))
|
||||||
|
return
|
||||||
|
}
|
||||||
c.SetSession("verifiedCode", "")
|
c.SetSession("verifiedCode", "")
|
||||||
|
c.SetSession("verifiedUserId", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
targetUser, err := object.GetUser(userId)
|
targetUser, err := object.GetUser(userId)
|
||||||
@ -519,11 +541,29 @@ func (c *ApiController) SetPassword() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
application, err := object.GetApplicationByUser(targetUser)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if application == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf(c.T("auth:the application for user %s is not found"), userId))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
|
||||||
|
err = object.CheckEntryIp(clientIp, targetUser, application, organization, c.GetAcceptLanguage())
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
targetUser.Password = newPassword
|
targetUser.Password = newPassword
|
||||||
targetUser.UpdateUserPassword(organization)
|
targetUser.UpdateUserPassword(organization)
|
||||||
targetUser.NeedUpdatePassword = false
|
targetUser.NeedUpdatePassword = false
|
||||||
|
targetUser.LastChangePasswordTime = util.GetCurrentTime()
|
||||||
|
|
||||||
_, err = object.UpdateUser(userId, targetUser, []string{"password", "need_update_password", "password_type"}, false)
|
_, err = object.UpdateUser(userId, targetUser, []string{"password", "need_update_password", "password_type", "last_change_password_time"}, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
|
@ -45,6 +45,22 @@ func (c *ApiController) ResponseOk(data ...interface{}) {
|
|||||||
|
|
||||||
// ResponseError ...
|
// ResponseError ...
|
||||||
func (c *ApiController) ResponseError(error string, data ...interface{}) {
|
func (c *ApiController) ResponseError(error string, data ...interface{}) {
|
||||||
|
enableErrorMask2 := conf.GetConfigBool("enableErrorMask2")
|
||||||
|
if enableErrorMask2 {
|
||||||
|
error = c.T("subscription:Error")
|
||||||
|
|
||||||
|
resp := &Response{Status: "error", Msg: error}
|
||||||
|
c.ResponseJsonData(resp, data...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
enableErrorMask := conf.GetConfigBool("enableErrorMask")
|
||||||
|
if enableErrorMask {
|
||||||
|
if strings.HasPrefix(error, "The user: ") && strings.HasSuffix(error, " doesn't exist") || strings.HasPrefix(error, "用户: ") && strings.HasSuffix(error, "不存在") {
|
||||||
|
error = c.T("check:password or code is incorrect")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resp := &Response{Status: "error", Msg: error}
|
resp := &Response{Status: "error", Msg: error}
|
||||||
c.ResponseJsonData(resp, data...)
|
c.ResponseJsonData(resp, data...)
|
||||||
}
|
}
|
||||||
|
@ -132,7 +132,8 @@ func (c *ApiController) SendVerificationCode() {
|
|||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
remoteAddr := util.GetIPFromRequest(c.Ctx.Request)
|
|
||||||
|
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
|
||||||
|
|
||||||
if msg := vform.CheckParameter(form.SendVerifyCode, c.GetAcceptLanguage()); msg != "" {
|
if msg := vform.CheckParameter(form.SendVerifyCode, c.GetAcceptLanguage()); msg != "" {
|
||||||
c.ResponseError(msg)
|
c.ResponseError(msg)
|
||||||
@ -245,8 +246,6 @@ func (c *ApiController) SendVerificationCode() {
|
|||||||
if user != nil && util.GetMaskedEmail(mfaProps.Secret) == vform.Dest {
|
if user != nil && util.GetMaskedEmail(mfaProps.Secret) == vform.Dest {
|
||||||
vform.Dest = mfaProps.Secret
|
vform.Dest = mfaProps.Secret
|
||||||
}
|
}
|
||||||
} else if vform.Method == MfaSetupVerification {
|
|
||||||
c.SetSession(MfaDestSession, vform.Dest)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
provider, err = application.GetEmailProvider(vform.Method)
|
provider, err = application.GetEmailProvider(vform.Method)
|
||||||
@ -259,7 +258,7 @@ func (c *ApiController) SendVerificationCode() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, vform.Dest)
|
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, clientIp, vform.Dest)
|
||||||
case object.VerifyTypePhone:
|
case object.VerifyTypePhone:
|
||||||
if vform.Method == LoginVerification || vform.Method == ForgetVerification {
|
if vform.Method == LoginVerification || vform.Method == ForgetVerification {
|
||||||
if user != nil && util.GetMaskedPhone(user.Phone) == vform.Dest {
|
if user != nil && util.GetMaskedPhone(user.Phone) == vform.Dest {
|
||||||
@ -281,11 +280,6 @@ func (c *ApiController) SendVerificationCode() {
|
|||||||
vform.CountryCode = user.GetCountryCode(vform.CountryCode)
|
vform.CountryCode = user.GetCountryCode(vform.CountryCode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if vform.Method == MfaSetupVerification {
|
|
||||||
c.SetSession(MfaCountryCodeSession, vform.CountryCode)
|
|
||||||
c.SetSession(MfaDestSession, vform.Dest)
|
|
||||||
}
|
|
||||||
} else if vform.Method == MfaAuthVerification {
|
} else if vform.Method == MfaAuthVerification {
|
||||||
mfaProps := user.GetPreferredMfaProps(false)
|
mfaProps := user.GetPreferredMfaProps(false)
|
||||||
if user != nil && util.GetMaskedPhone(mfaProps.Secret) == vform.Dest {
|
if user != nil && util.GetMaskedPhone(mfaProps.Secret) == vform.Dest {
|
||||||
@ -293,6 +287,7 @@ func (c *ApiController) SendVerificationCode() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
vform.CountryCode = mfaProps.CountryCode
|
vform.CountryCode = mfaProps.CountryCode
|
||||||
|
vform.CountryCode = user.GetCountryCode(vform.CountryCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
provider, err = application.GetSmsProvider(vform.Method, vform.CountryCode)
|
provider, err = application.GetSmsProvider(vform.Method, vform.CountryCode)
|
||||||
@ -309,7 +304,7 @@ func (c *ApiController) SendVerificationCode() {
|
|||||||
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), vform.CountryCode))
|
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), vform.CountryCode))
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
sendResp = object.SendVerificationCodeToPhone(organization, user, provider, remoteAddr, phone)
|
sendResp = object.SendVerificationCodeToPhone(organization, user, provider, clientIp, phone)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -532,5 +527,6 @@ func (c *ApiController) VerifyCode() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.SetSession("verifiedCode", authForm.Code)
|
c.SetSession("verifiedCode", authForm.Code)
|
||||||
|
c.SetSession("verifiedUserId", user.GetId())
|
||||||
c.ResponseOk()
|
c.ResponseOk()
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,18 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func deployStaticFiles(provider *object.Provider) {
|
func deployStaticFiles(provider *object.Provider) {
|
||||||
storageProvider, err := storage.GetStorageProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.RegionId, provider.Bucket, provider.Endpoint)
|
certificate := ""
|
||||||
|
if provider.Category == "Storage" && provider.Type == "Casdoor" {
|
||||||
|
cert, err := object.GetCert(util.GetId(provider.Owner, provider.Cert))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if cert == nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
certificate = cert.Certificate
|
||||||
|
}
|
||||||
|
storageProvider, err := storage.GetStorageProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.RegionId, provider.Bucket, provider.Endpoint, certificate, provider.Content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,10 @@ type AuthForm struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
FirstName string `json:"firstName"`
|
FirstName string `json:"firstName"`
|
||||||
LastName string `json:"lastName"`
|
LastName string `json:"lastName"`
|
||||||
|
Gender string `json:"gender"`
|
||||||
|
Bio string `json:"bio"`
|
||||||
|
Tag string `json:"tag"`
|
||||||
|
Education string `json:"education"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Phone string `json:"phone"`
|
Phone string `json:"phone"`
|
||||||
Affiliation string `json:"affiliation"`
|
Affiliation string `json:"affiliation"`
|
||||||
|
10
go.mod
10
go.mod
@ -6,13 +6,14 @@ require (
|
|||||||
github.com/Masterminds/squirrel v1.5.3
|
github.com/Masterminds/squirrel v1.5.3
|
||||||
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387
|
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387
|
||||||
github.com/aws/aws-sdk-go v1.45.5
|
github.com/aws/aws-sdk-go v1.45.5
|
||||||
github.com/beego/beego v1.12.13
|
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/v2 v2.77.2
|
github.com/casbin/casbin/v2 v2.77.2
|
||||||
github.com/casdoor/go-sms-sender v0.24.0
|
github.com/casdoor/go-sms-sender v0.25.0
|
||||||
github.com/casdoor/gomail/v2 v2.0.1
|
github.com/casdoor/gomail/v2 v2.0.1
|
||||||
|
github.com/casdoor/ldapserver v1.2.0
|
||||||
github.com/casdoor/notify v0.45.0
|
github.com/casdoor/notify v0.45.0
|
||||||
github.com/casdoor/oss v1.6.0
|
github.com/casdoor/oss v1.8.0
|
||||||
github.com/casdoor/xorm-adapter/v3 v3.1.0
|
github.com/casdoor/xorm-adapter/v3 v3.1.0
|
||||||
github.com/casvisor/casvisor-go-sdk v1.4.0
|
github.com/casvisor/casvisor-go-sdk v1.4.0
|
||||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||||
@ -20,7 +21,6 @@ require (
|
|||||||
github.com/elazarl/go-bindata-assetfs v1.0.1 // indirect
|
github.com/elazarl/go-bindata-assetfs v1.0.1 // indirect
|
||||||
github.com/elimity-com/scim v0.0.0-20230426070224-941a5eac92f3
|
github.com/elimity-com/scim v0.0.0-20230426070224-941a5eac92f3
|
||||||
github.com/fogleman/gg v1.3.0
|
github.com/fogleman/gg v1.3.0
|
||||||
github.com/forestmgy/ldapserver v1.1.0
|
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.5
|
github.com/go-asn1-ber/asn1-ber v1.5.5
|
||||||
github.com/go-git/go-git/v5 v5.11.0
|
github.com/go-git/go-git/v5 v5.11.0
|
||||||
github.com/go-ldap/ldap/v3 v3.4.6
|
github.com/go-ldap/ldap/v3 v3.4.6
|
||||||
@ -30,7 +30,7 @@ require (
|
|||||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
|
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
|
||||||
github.com/go-webauthn/webauthn v0.6.0
|
github.com/go-webauthn/webauthn v0.6.0
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||||
github.com/google/uuid v1.4.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/json-iterator/go v1.1.12
|
github.com/json-iterator/go v1.1.12
|
||||||
github.com/lestrrat-go/jwx v1.2.29
|
github.com/lestrrat-go/jwx v1.2.29
|
||||||
github.com/lib/pq v1.10.9
|
github.com/lib/pq v1.10.9
|
||||||
|
21
go.sum
21
go.sum
@ -1052,8 +1052,8 @@ github.com/baidubce/bce-sdk-go v0.9.156 h1:f++WfptxGmSp5acsjl4kUxHpWDDccoFqkIrQK
|
|||||||
github.com/baidubce/bce-sdk-go v0.9.156/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg=
|
github.com/baidubce/bce-sdk-go v0.9.156/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg=
|
||||||
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.13 h1:g39O1LGLTiPejWVqQKK/TFGrroW9BCZQz6/pf4S8IRM=
|
github.com/beego/beego v1.12.12 h1:ARY1sNVSS23N0mEQIhSqRDTyyDlx95JY0V3GogBbZbQ=
|
||||||
github.com/beego/beego v1.12.13/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=
|
||||||
@ -1083,16 +1083,20 @@ github.com/casbin/casbin/v2 v2.28.3/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRt
|
|||||||
github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
|
github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
|
||||||
github.com/casbin/casbin/v2 v2.77.2 h1:yQinn/w9x8AswiwqwtrXz93VU48R1aYTXdHEx4RI3jM=
|
github.com/casbin/casbin/v2 v2.77.2 h1:yQinn/w9x8AswiwqwtrXz93VU48R1aYTXdHEx4RI3jM=
|
||||||
github.com/casbin/casbin/v2 v2.77.2/go.mod h1:mzGx0hYW9/ksOSpw3wNjk3NRAroq5VMFYUQ6G43iGPk=
|
github.com/casbin/casbin/v2 v2.77.2/go.mod h1:mzGx0hYW9/ksOSpw3wNjk3NRAroq5VMFYUQ6G43iGPk=
|
||||||
|
github.com/casdoor/casdoor-go-sdk v0.50.0 h1:bUYbz/MzJuWfLKJbJM0+U0YpYewAur+THp5TKnufWZM=
|
||||||
|
github.com/casdoor/casdoor-go-sdk v0.50.0/go.mod h1:cMnkCQJgMYpgAlgEx8reSt1AVaDIQLcJ1zk5pzBaz+4=
|
||||||
github.com/casdoor/go-reddit/v2 v2.1.0 h1:kIbfdJ7AA7H0uTQ8s0q4GGZqSS5V9wVE74RrXyD9XPs=
|
github.com/casdoor/go-reddit/v2 v2.1.0 h1:kIbfdJ7AA7H0uTQ8s0q4GGZqSS5V9wVE74RrXyD9XPs=
|
||||||
github.com/casdoor/go-reddit/v2 v2.1.0/go.mod h1:eagkvwlZ4Hcsuc/uQsLHYEulz5jN65SVSwV/AIE7zsc=
|
github.com/casdoor/go-reddit/v2 v2.1.0/go.mod h1:eagkvwlZ4Hcsuc/uQsLHYEulz5jN65SVSwV/AIE7zsc=
|
||||||
github.com/casdoor/go-sms-sender v0.24.0 h1:LNLsce3EG/87I3JS6UiajF3LlQmdIiCgebEu0IE4wSM=
|
github.com/casdoor/go-sms-sender v0.25.0 h1:eF4cOCSbjVg7+0uLlJQnna/FQ0BWW+Fp/x4cXhzQu1Y=
|
||||||
github.com/casdoor/go-sms-sender v0.24.0/go.mod h1:bOm4H8/YfJmEHjBatEVQFOnAf0OOn1B0Wi5B7zDhws0=
|
github.com/casdoor/go-sms-sender v0.25.0/go.mod h1:bOm4H8/YfJmEHjBatEVQFOnAf0OOn1B0Wi5B7zDhws0=
|
||||||
github.com/casdoor/gomail/v2 v2.0.1 h1:J+FG6x80s9e5lBHUn8Sv0Y56mud34KiWih5YdmudR/w=
|
github.com/casdoor/gomail/v2 v2.0.1 h1:J+FG6x80s9e5lBHUn8Sv0Y56mud34KiWih5YdmudR/w=
|
||||||
github.com/casdoor/gomail/v2 v2.0.1/go.mod h1:VnGPslEAtpix5FjHisR/WKB1qvZDBaujbikxDe9d+2Q=
|
github.com/casdoor/gomail/v2 v2.0.1/go.mod h1:VnGPslEAtpix5FjHisR/WKB1qvZDBaujbikxDe9d+2Q=
|
||||||
|
github.com/casdoor/ldapserver v1.2.0 h1:HdSYe+ULU6z9K+2BqgTrJKQRR4//ERAXB64ttOun6Ow=
|
||||||
|
github.com/casdoor/ldapserver v1.2.0/go.mod h1:VwYU2vqQ2pA8sa00PRekH71R2XmgfzMKhmp1XrrDu2s=
|
||||||
github.com/casdoor/notify v0.45.0 h1:OlaFvcQFjGOgA4mRx07M8AH1gvb5xNo21mcqrVGlLgk=
|
github.com/casdoor/notify v0.45.0 h1:OlaFvcQFjGOgA4mRx07M8AH1gvb5xNo21mcqrVGlLgk=
|
||||||
github.com/casdoor/notify v0.45.0/go.mod h1:wNHQu0tiDROMBIvz0j3Om3Lhd5yZ+AIfnFb8MYb8OLQ=
|
github.com/casdoor/notify v0.45.0/go.mod h1:wNHQu0tiDROMBIvz0j3Om3Lhd5yZ+AIfnFb8MYb8OLQ=
|
||||||
github.com/casdoor/oss v1.6.0 h1:IOWrGLJ+VO82qS796eaRnzFPPA1Sn3cotYTi7O/VIlQ=
|
github.com/casdoor/oss v1.8.0 h1:uuyKhDIp7ydOtV4lpqhAY23Ban2Ln8La8+QT36CwylM=
|
||||||
github.com/casdoor/oss v1.6.0/go.mod h1:rJAWA0hLhtu94t6IRpotLUkXO1NWMASirywQYaGizJE=
|
github.com/casdoor/oss v1.8.0/go.mod h1:uaqO7KBI2lnZcnB8rF7O6C2bN7llIbfC5Ql8ex1yR1U=
|
||||||
github.com/casdoor/xorm-adapter/v3 v3.1.0 h1:NodWayRtSLVSeCvL9H3Hc61k0G17KhV9IymTCNfh3kk=
|
github.com/casdoor/xorm-adapter/v3 v3.1.0 h1:NodWayRtSLVSeCvL9H3Hc61k0G17KhV9IymTCNfh3kk=
|
||||||
github.com/casdoor/xorm-adapter/v3 v3.1.0/go.mod h1:4WTcUw+bTgBylGHeGHzTtBvuTXRS23dtwzFLl9tsgFM=
|
github.com/casdoor/xorm-adapter/v3 v3.1.0/go.mod h1:4WTcUw+bTgBylGHeGHzTtBvuTXRS23dtwzFLl9tsgFM=
|
||||||
github.com/casvisor/casvisor-go-sdk v1.4.0 h1:hbZEGGJ1cwdHFAxeXrMoNw6yha6Oyg2F0qQhBNCN/dg=
|
github.com/casvisor/casvisor-go-sdk v1.4.0 h1:hbZEGGJ1cwdHFAxeXrMoNw6yha6Oyg2F0qQhBNCN/dg=
|
||||||
@ -1235,8 +1239,6 @@ github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga
|
|||||||
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||||
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
|
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
|
||||||
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||||
github.com/forestmgy/ldapserver v1.1.0 h1:gvil4nuLhqPEL8SugCkFhRyA0/lIvRdwZSqlrw63ll4=
|
|
||||||
github.com/forestmgy/ldapserver v1.1.0/go.mod h1:1RZ8lox1QSY7rmbjdmy+sYQXY4Lp7SpGzpdE3+j3IyM=
|
|
||||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
|
github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
|
||||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||||
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
||||||
@ -1460,8 +1462,9 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
|
|||||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
|
||||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
|
github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
|
github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg=
|
github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg=
|
||||||
|
@ -1,167 +1,167 @@
|
|||||||
{
|
{
|
||||||
"account": {
|
"account": {
|
||||||
"Failed to add user": "Failed to add user",
|
"Failed to add user": "عدم موفقیت در افزودن کاربر",
|
||||||
"Get init score failed, error: %w": "Get init score failed, error: %w",
|
"Get init score failed, error: %w": "عدم موفقیت در دریافت امتیاز اولیه، خطا: %w",
|
||||||
"Please sign out first": "Please sign out first",
|
"Please sign out first": "لطفاً ابتدا خارج شوید",
|
||||||
"The application does not allow to sign up new account": "The application does not allow to sign up new account"
|
"The application does not allow to sign up new account": "برنامه اجازه ثبتنام حساب جدید را نمیدهد"
|
||||||
},
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"Challenge method should be S256": "Challenge method should be S256",
|
"Challenge method should be S256": "روش چالش باید S256 باشد",
|
||||||
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s",
|
"Failed to create user, user information is invalid: %s": "عدم موفقیت در ایجاد کاربر، اطلاعات کاربر نامعتبر است: %s",
|
||||||
"Failed to login in: %s": "Failed to login in: %s",
|
"Failed to login in: %s": "عدم موفقیت در ورود: %s",
|
||||||
"Invalid token": "Invalid token",
|
"Invalid token": "توکن نامعتبر",
|
||||||
"State expected: %s, but got: %s": "State expected: %s, but got: %s",
|
"State expected: %s, but got: %s": "وضعیت مورد انتظار: %s، اما دریافت شد: %s",
|
||||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up",
|
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up": "حساب برای ارائهدهنده: %s و نام کاربری: %s (%s) وجود ندارد و مجاز به ثبتنام بهعنوان حساب جدید از طریق %%s نیست، لطفاً از روش دیگری برای ثبتنام استفاده کنید",
|
||||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
|
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "حساب برای ارائهدهنده: %s و نام کاربری: %s (%s) وجود ندارد و مجاز به ثبتنام بهعنوان حساب جدید نیست، لطفاً با پشتیبانی IT خود تماس بگیرید",
|
||||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
|
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "حساب برای ارائهدهنده: %s و نام کاربری: %s (%s) در حال حاضر به حساب دیگری مرتبط است: %s (%s)",
|
||||||
"The application: %s does not exist": "The application: %s does not exist",
|
"The application: %s does not exist": "برنامه: %s وجود ندارد",
|
||||||
"The login method: login with LDAP is not enabled for the application": "The login method: login with LDAP is not enabled for the application",
|
"The login method: login with LDAP is not enabled for the application": "روش ورود: ورود با LDAP برای برنامه فعال نیست",
|
||||||
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
|
"The login method: login with SMS is not enabled for the application": "روش ورود: ورود با پیامک برای برنامه فعال نیست",
|
||||||
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
|
"The login method: login with email is not enabled for the application": "روش ورود: ورود با ایمیل برای برنامه فعال نیست",
|
||||||
"The login method: login with face is not enabled for the application": "The login method: login with face is not enabled for the application",
|
"The login method: login with face is not enabled for the application": "روش ورود: ورود با چهره برای برنامه فعال نیست",
|
||||||
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
|
"The login method: login with password is not enabled for the application": "روش ورود: ورود با رمز عبور برای برنامه فعال نیست",
|
||||||
"The organization: %s does not exist": "The organization: %s does not exist",
|
"The organization: %s does not exist": "سازمان: %s وجود ندارد",
|
||||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
"The provider: %s is not enabled for the application": "ارائهدهنده: %s برای برنامه فعال نیست",
|
||||||
"Unauthorized operation": "Unauthorized operation",
|
"Unauthorized operation": "عملیات غیرمجاز",
|
||||||
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
|
"Unknown authentication type (not password or provider), form = %s": "نوع احراز هویت ناشناخته (نه رمز عبور و نه ارائهدهنده)، فرم = %s",
|
||||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags",
|
"User's tag: %s is not listed in the application's tags": "برچسب کاربر: %s در برچسبهای برنامه فهرست نشده است",
|
||||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "کاربر پرداختی %s اشتراک فعال یا در انتظار ندارد و برنامه: %s قیمتگذاری پیشفرض ندارد"
|
||||||
},
|
},
|
||||||
"cas": {
|
"cas": {
|
||||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
"Service %s and %s do not match": "سرویس %s و %s مطابقت ندارند"
|
||||||
},
|
},
|
||||||
"check": {
|
"check": {
|
||||||
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
"Affiliation cannot be blank": "وابستگی نمیتواند خالی باشد",
|
||||||
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
|
"Default code does not match the code's matching rules": "کد پیشفرض با قوانین تطبیق کد مطابقت ندارد",
|
||||||
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
"DisplayName cannot be blank": "نام نمایشی نمیتواند خالی باشد",
|
||||||
"DisplayName is not valid real name": "DisplayName is not valid real name",
|
"DisplayName is not valid real name": "نام نمایشی یک نام واقعی معتبر نیست",
|
||||||
"Email already exists": "Email already exists",
|
"Email already exists": "ایمیل قبلاً وجود دارد",
|
||||||
"Email cannot be empty": "Email cannot be empty",
|
"Email cannot be empty": "ایمیل نمیتواند خالی باشد",
|
||||||
"Email is invalid": "Email is invalid",
|
"Email is invalid": "ایمیل نامعتبر است",
|
||||||
"Empty username.": "Empty username.",
|
"Empty username.": "نام کاربری خالی است.",
|
||||||
"Face data does not exist, cannot log in": "Face data does not exist, cannot log in",
|
"Face data does not exist, cannot log in": "دادههای چهره وجود ندارد، نمیتوان وارد شد",
|
||||||
"Face data mismatch": "Face data mismatch",
|
"Face data mismatch": "عدم تطابق دادههای چهره",
|
||||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
"FirstName cannot be blank": "نام نمیتواند خالی باشد",
|
||||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
"Invitation code cannot be blank": "کد دعوت نمیتواند خالی باشد",
|
||||||
"Invitation code exhausted": "Invitation code exhausted",
|
"Invitation code exhausted": "کد دعوت استفاده شده است",
|
||||||
"Invitation code is invalid": "Invitation code is invalid",
|
"Invitation code is invalid": "کد دعوت نامعتبر است",
|
||||||
"Invitation code suspended": "Invitation code suspended",
|
"Invitation code suspended": "کد دعوت معلق است",
|
||||||
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
"LDAP user name or password incorrect": "نام کاربری یا رمز عبور LDAP نادرست است",
|
||||||
"LastName cannot be blank": "LastName cannot be blank",
|
"LastName cannot be blank": "نام خانوادگی نمیتواند خالی باشد",
|
||||||
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
"Multiple accounts with same uid, please check your ldap server": "چندین حساب با uid یکسان، لطفاً سرور LDAP خود را بررسی کنید",
|
||||||
"Organization does not exist": "Organization does not exist",
|
"Organization does not exist": "سازمان وجود ندارد",
|
||||||
"Phone already exists": "Phone already exists",
|
"Phone already exists": "تلفن قبلاً وجود دارد",
|
||||||
"Phone cannot be empty": "Phone cannot be empty",
|
"Phone cannot be empty": "تلفن نمیتواند خالی باشد",
|
||||||
"Phone number is invalid": "Phone number is invalid",
|
"Phone number is invalid": "شماره تلفن نامعتبر است",
|
||||||
"Please register using the email corresponding to the invitation code": "Please register using the email corresponding to the invitation code",
|
"Please register using the email corresponding to the invitation code": "لطفاً با استفاده از ایمیل مربوط به کد دعوت ثبتنام کنید",
|
||||||
"Please register using the phone corresponding to the invitation code": "Please register using the phone corresponding to the invitation code",
|
"Please register using the phone corresponding to the invitation code": "لطفاً با استفاده از تلفن مربوط به کد دعوت ثبتنام کنید",
|
||||||
"Please register using the username corresponding to the invitation code": "Please register using the username corresponding to the invitation code",
|
"Please register using the username corresponding to the invitation code": "لطفاً با استفاده از نام کاربری مربوط به کد دعوت ثبتنام کنید",
|
||||||
"Session outdated, please login again": "Session outdated, please login again",
|
"Session outdated, please login again": "جلسه منقضی شده است، لطفاً دوباره وارد شوید",
|
||||||
"The invitation code has already been used": "The invitation code has already been used",
|
"The invitation code has already been used": "کد دعوت قبلاً استفاده شده است",
|
||||||
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
|
"The user is forbidden to sign in, please contact the administrator": "ورود کاربر ممنوع است، لطفاً با مدیر تماس بگیرید",
|
||||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
"The user: %s doesn't exist in LDAP server": "کاربر: %s در سرور LDAP وجود ندارد",
|
||||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.",
|
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "نام کاربری فقط میتواند حاوی کاراکترهای الفبایی عددی، زیرخط یا خط تیره باشد، نمیتواند خط تیره یا زیرخط متوالی داشته باشد، و نمیتواند با خط تیره یا زیرخط شروع یا پایان یابد.",
|
||||||
"The value \\\"%s\\\" for account field \\\"%s\\\" doesn't match the account item regex": "The value \\\"%s\\\" for account field \\\"%s\\\" doesn't match the account item regex",
|
"The value \"%s\" for account field \"%s\" doesn't match the account item regex": "مقدار \"%s\" برای فیلد حساب \"%s\" با عبارت منظم مورد حساب مطابقت ندارد",
|
||||||
"The value \\\"%s\\\" for signup field \\\"%s\\\" doesn't match the signup item regex of the application \\\"%s\\\"": "The value \\\"%s\\\" for signup field \\\"%s\\\" doesn't match the signup item regex of the application \\\"%s\\\"",
|
"The value \"%s\" for signup field \"%s\" doesn't match the signup item regex of the application \"%s\"": "مقدار \"%s\" برای فیلد ثبتنام \"%s\" با عبارت منظم مورد ثبتنام برنامه \"%s\" مطابقت ندارد",
|
||||||
"Username already exists": "Username already exists",
|
"Username already exists": "نام کاربری قبلاً وجود دارد",
|
||||||
"Username cannot be an email address": "Username cannot be an email address",
|
"Username cannot be an email address": "نام کاربری نمیتواند یک آدرس ایمیل باشد",
|
||||||
"Username cannot contain white spaces": "Username cannot contain white spaces",
|
"Username cannot contain white spaces": "نام کاربری نمیتواند حاوی فاصله باشد",
|
||||||
"Username cannot start with a digit": "Username cannot start with a digit",
|
"Username cannot start with a digit": "نام کاربری نمیتواند با یک رقم شروع شود",
|
||||||
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
|
"Username is too long (maximum is 39 characters).": "نام کاربری بیش از حد طولانی است (حداکثر ۳۹ کاراکتر).",
|
||||||
"Username must have at least 2 characters": "Username must have at least 2 characters",
|
"Username must have at least 2 characters": "نام کاربری باید حداقل ۲ کاراکتر داشته باشد",
|
||||||
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
|
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "شما رمز عبور یا کد اشتباه را بیش از حد وارد کردهاید، لطفاً %d دقیقه صبر کنید و دوباره تلاش کنید",
|
||||||
"Your region is not allow to signup by phone": "Your region is not allow to signup by phone",
|
"Your region is not allow to signup by phone": "منطقه شما اجازه ثبتنام با تلفن را ندارد",
|
||||||
"password or code is incorrect": "password or code is incorrect",
|
"password or code is incorrect": "رمز عبور یا کد نادرست است",
|
||||||
"password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
|
"password or code is incorrect, you have %d remaining chances": "رمز عبور یا کد نادرست است، شما %d فرصت باقیمانده دارید",
|
||||||
"unsupported password type: %s": "unsupported password type: %s"
|
"unsupported password type: %s": "نوع رمز عبور پشتیبانی نشده: %s"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
"Missing parameter": "Missing parameter",
|
"Missing parameter": "پارامتر گمشده",
|
||||||
"Please login first": "Please login first",
|
"Please login first": "لطفاً ابتدا وارد شوید",
|
||||||
"The organization: %s should have one application at least": "The organization: %s should have one application at least",
|
"The organization: %s should have one application at least": "سازمان: %s باید حداقل یک برنامه داشته باشد",
|
||||||
"The user: %s doesn't exist": "The user: %s doesn't exist",
|
"The user: %s doesn't exist": "کاربر: %s وجود ندارد",
|
||||||
"don't support captchaProvider: ": "don't support captchaProvider: ",
|
"don't support captchaProvider: ": "از captchaProvider پشتیبانی نمیشود: ",
|
||||||
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode",
|
"this operation is not allowed in demo mode": "این عملیات در حالت دمو مجاز نیست",
|
||||||
"this operation requires administrator to perform": "this operation requires administrator to perform"
|
"this operation requires administrator to perform": "این عملیات نیاز به مدیر برای انجام دارد"
|
||||||
},
|
},
|
||||||
"ldap": {
|
"ldap": {
|
||||||
"Ldap server exist": "Ldap server exist"
|
"Ldap server exist": "سرور LDAP وجود دارد"
|
||||||
},
|
},
|
||||||
"link": {
|
"link": {
|
||||||
"Please link first": "Please link first",
|
"Please link first": "لطفاً ابتدا پیوند دهید",
|
||||||
"This application has no providers": "This application has no providers",
|
"This application has no providers": "این برنامه ارائهدهندهای ندارد",
|
||||||
"This application has no providers of type": "This application has no providers of type",
|
"This application has no providers of type": "این برنامه ارائهدهندهای از نوع ندارد",
|
||||||
"This provider can't be unlinked": "This provider can't be unlinked",
|
"This provider can't be unlinked": "این ارائهدهنده نمیتواند لغو پیوند شود",
|
||||||
"You are not the global admin, you can't unlink other users": "You are not the global admin, you can't unlink other users",
|
"You are not the global admin, you can't unlink other users": "شما مدیر جهانی نیستید، نمیتوانید کاربران دیگر را لغو پیوند کنید",
|
||||||
"You can't unlink yourself, you are not a member of any application": "You can't unlink yourself, you are not a member of any application"
|
"You can't unlink yourself, you are not a member of any application": "شما نمیتوانید خودتان را لغو پیوند کنید، شما عضو هیچ برنامهای نیستید"
|
||||||
},
|
},
|
||||||
"organization": {
|
"organization": {
|
||||||
"Only admin can modify the %s.": "Only admin can modify the %s.",
|
"Only admin can modify the %s.": "فقط مدیر میتواند %s را تغییر دهد.",
|
||||||
"The %s is immutable.": "The %s is immutable.",
|
"The %s is immutable.": "%s غیرقابل تغییر است.",
|
||||||
"Unknown modify rule %s.": "Unknown modify rule %s."
|
"Unknown modify rule %s.": "قانون تغییر ناشناخته %s."
|
||||||
},
|
},
|
||||||
"permission": {
|
"permission": {
|
||||||
"The permission: \\\"%s\\\" doesn't exist": "The permission: \\\"%s\\\" doesn't exist"
|
"The permission: \"%s\" doesn't exist": "مجوز: \"%s\" وجود ندارد"
|
||||||
},
|
},
|
||||||
"provider": {
|
"provider": {
|
||||||
"Invalid application id": "Invalid application id",
|
"Invalid application id": "شناسه برنامه نامعتبر",
|
||||||
"the provider: %s does not exist": "the provider: %s does not exist"
|
"the provider: %s does not exist": "ارائهدهنده: %s وجود ندارد"
|
||||||
},
|
},
|
||||||
"resource": {
|
"resource": {
|
||||||
"User is nil for tag: avatar": "User is nil for tag: avatar",
|
"User is nil for tag: avatar": "کاربر برای برچسب: آواتار تهی است",
|
||||||
"Username or fullFilePath is empty: username = %s, fullFilePath = %s": "Username or fullFilePath is empty: username = %s, fullFilePath = %s"
|
"Username or fullFilePath is empty: username = %s, fullFilePath = %s": "نام کاربری یا مسیر کامل فایل خالی است: نام کاربری = %s، مسیر کامل فایل = %s"
|
||||||
},
|
},
|
||||||
"saml": {
|
"saml": {
|
||||||
"Application %s not found": "Application %s not found"
|
"Application %s not found": "برنامه %s یافت نشد"
|
||||||
},
|
},
|
||||||
"saml_sp": {
|
"saml_sp": {
|
||||||
"provider %s's category is not SAML": "provider %s's category is not SAML"
|
"provider %s's category is not SAML": "دستهبندی ارائهدهنده %s SAML نیست"
|
||||||
},
|
},
|
||||||
"service": {
|
"service": {
|
||||||
"Empty parameters for emailForm: %v": "Empty parameters for emailForm: %v",
|
"Empty parameters for emailForm: %v": "پارامترهای خالی برای emailForm: %v",
|
||||||
"Invalid Email receivers: %s": "Invalid Email receivers: %s",
|
"Invalid Email receivers: %s": "گیرندگان ایمیل نامعتبر: %s",
|
||||||
"Invalid phone receivers: %s": "Invalid phone receivers: %s"
|
"Invalid phone receivers: %s": "گیرندگان تلفن نامعتبر: %s"
|
||||||
},
|
},
|
||||||
"storage": {
|
"storage": {
|
||||||
"The objectKey: %s is not allowed": "The objectKey: %s is not allowed",
|
"The objectKey: %s is not allowed": "objectKey: %s مجاز نیست",
|
||||||
"The provider type: %s is not supported": "The provider type: %s is not supported"
|
"The provider type: %s is not supported": "نوع ارائهدهنده: %s پشتیبانی نمیشود"
|
||||||
},
|
},
|
||||||
"token": {
|
"token": {
|
||||||
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application",
|
"Grant_type: %s is not supported in this application": "grant_type: %s در این برنامه پشتیبانی نمیشود",
|
||||||
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret",
|
"Invalid application or wrong clientSecret": "برنامه نامعتبر یا clientSecret نادرست",
|
||||||
"Invalid client_id": "Invalid client_id",
|
"Invalid client_id": "client_id نامعتبر",
|
||||||
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list",
|
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "آدرس بازگشت: %s در لیست آدرسهای بازگشت مجاز وجود ندارد",
|
||||||
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
|
"Token not found, invalid accessToken": "توکن یافت نشد، accessToken نامعتبر"
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"Display name cannot be empty": "Display name cannot be empty",
|
"Display name cannot be empty": "نام نمایشی نمیتواند خالی باشد",
|
||||||
"New password cannot contain blank space.": "New password cannot contain blank space."
|
"New password cannot contain blank space.": "رمز عبور جدید نمیتواند حاوی فاصله خالی باشد."
|
||||||
},
|
},
|
||||||
"user_upload": {
|
"user_upload": {
|
||||||
"Failed to import users": "Failed to import users"
|
"Failed to import users": "عدم موفقیت در وارد کردن کاربران"
|
||||||
},
|
},
|
||||||
"util": {
|
"util": {
|
||||||
"No application is found for userId: %s": "No application is found for userId: %s",
|
"No application is found for userId: %s": "هیچ برنامهای برای userId: %s یافت نشد",
|
||||||
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
"No provider for category: %s is found for application: %s": "هیچ ارائهدهندهای برای دستهبندی: %s برای برنامه: %s یافت نشد",
|
||||||
"The provider: %s is not found": "The provider: %s is not found"
|
"The provider: %s is not found": "ارائهدهنده: %s یافت نشد"
|
||||||
},
|
},
|
||||||
"verification": {
|
"verification": {
|
||||||
"Invalid captcha provider.": "Invalid captcha provider.",
|
"Invalid captcha provider.": "ارائهدهنده کپچا نامعتبر.",
|
||||||
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
|
"Phone number is invalid in your region %s": "شماره تلفن در منطقه شما نامعتبر است %s",
|
||||||
"The verification code has not been sent yet!": "The verification code has not been sent yet!",
|
"The verification code has not been sent yet!": "کد تأیید هنوز ارسال نشده است!",
|
||||||
"The verification code has not been sent yet, or has already been used!": "The verification code has not been sent yet, or has already been used!",
|
"The verification code has not been sent yet, or has already been used!": "کد تأیید هنوز ارسال نشده است، یا قبلاً استفاده شده است!",
|
||||||
"Turing test failed.": "Turing test failed.",
|
"Turing test failed.": "تست تورینگ ناموفق بود.",
|
||||||
"Unable to get the email modify rule.": "Unable to get the email modify rule.",
|
"Unable to get the email modify rule.": "عدم توانایی در دریافت قانون تغییر ایمیل.",
|
||||||
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
|
"Unable to get the phone modify rule.": "عدم توانایی در دریافت قانون تغییر تلفن.",
|
||||||
"Unknown type": "Unknown type",
|
"Unknown type": "نوع ناشناخته",
|
||||||
"Wrong verification code!": "Wrong verification code!",
|
"Wrong verification code!": "کد تأیید اشتباه!",
|
||||||
"You should verify your code in %d min!": "You should verify your code in %d min!",
|
"You should verify your code in %d min!": "شما باید کد خود را در %d دقیقه تأیید کنید!",
|
||||||
"please add a SMS provider to the \\\"Providers\\\" list for the application: %s": "please add a SMS provider to the \\\"Providers\\\" list for the application: %s",
|
"please add a SMS provider to the \"Providers\" list for the application: %s": "لطفاً یک ارائهدهنده پیامک به لیست \"ارائهدهندگان\" برای برنامه: %s اضافه کنید",
|
||||||
"please add an Email provider to the \\\"Providers\\\" list for the application: %s": "please add an Email provider to the \\\"Providers\\\" list for the application: %s",
|
"please add an Email provider to the \"Providers\" list for the application: %s": "لطفاً یک ارائهدهنده ایمیل به لیست \"ارائهدهندگان\" برای برنامه: %s اضافه کنید",
|
||||||
"the user does not exist, please sign up first": "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": "Found no credentials for this user",
|
"Found no credentials for this user": "هیچ اعتباری برای این کاربر یافت نشد",
|
||||||
"Please call WebAuthnSigninBegin first": "Please call WebAuthnSigninBegin first"
|
"Please call WebAuthnSigninBegin first": "لطفاً ابتدا WebAuthnSigninBegin را فراخوانی کنید"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,10 +15,10 @@
|
|||||||
"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": "Аккаунт для провайдера: %s и имя пользователя: %s (%s) не существует и не может быть зарегистрирован как новый аккаунт. Пожалуйста, обратитесь в службу поддержки IT",
|
"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": "Аккаунт для провайдера: %s и имя пользователя: %s (%s) не существует и не может быть зарегистрирован как новый аккаунт. Пожалуйста, обратитесь в службу поддержки IT",
|
||||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "Аккаунт поставщика: %s и имя пользователя: %s (%s) уже связаны с другим аккаунтом: %s (%s)",
|
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "Аккаунт поставщика: %s и имя пользователя: %s (%s) уже связаны с другим аккаунтом: %s (%s)",
|
||||||
"The application: %s does not exist": "Приложение: %s не существует",
|
"The application: %s does not exist": "Приложение: %s не существует",
|
||||||
"The login method: login with LDAP is not enabled for the application": "The login method: login with LDAP is not enabled for the application",
|
"The login method: login with LDAP is not enabled for the application": "Метод входа в систему: вход с помощью LDAP не включен для приложения",
|
||||||
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
|
"The login method: login with SMS is not enabled for the application": "Метод входа: вход с помощью SMS не включен для приложения",
|
||||||
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
|
"The login method: login with email is not enabled for the application": "Метод входа: вход с помощью электронной почты не включен для приложения",
|
||||||
"The login method: login with face is not enabled for the application": "The login method: login with face is not enabled for the application",
|
"The login method: login with face is not enabled for the application": "Метод входа: вход с помощью лица не включен для приложения",
|
||||||
"The login method: login with password is not enabled for the application": "Метод входа: вход с паролем не включен для приложения",
|
"The login method: login with password is not enabled for the application": "Метод входа: вход с паролем не включен для приложения",
|
||||||
"The organization: %s does not exist": "The organization: %s does not exist",
|
"The organization: %s does not exist": "The organization: %s does not exist",
|
||||||
"The provider: %s is not enabled for the application": "Провайдер: %s не включен для приложения",
|
"The provider: %s is not enabled for the application": "Провайдер: %s не включен для приложения",
|
||||||
@ -53,16 +53,16 @@
|
|||||||
"Phone already exists": "Телефон уже существует",
|
"Phone already exists": "Телефон уже существует",
|
||||||
"Phone cannot be empty": "Телефон не может быть пустым",
|
"Phone cannot be empty": "Телефон не может быть пустым",
|
||||||
"Phone number is invalid": "Номер телефона является недействительным",
|
"Phone number is invalid": "Номер телефона является недействительным",
|
||||||
"Please register using the email corresponding to the invitation code": "Please register using the email corresponding to the invitation code",
|
"Please register using the email corresponding to the invitation code": "Пожалуйста, зарегистрируйтесь, используя электронную почту, соответствующую коду приглашения",
|
||||||
"Please register using the phone corresponding to the invitation code": "Please register using the phone corresponding to the invitation code",
|
"Please register using the phone corresponding to the invitation code": "Пожалуйста, зарегистрируйтесь по телефону, соответствующему коду приглашения",
|
||||||
"Please register using the username corresponding to the invitation code": "Please register using the username corresponding to the invitation code",
|
"Please register using the username corresponding to the invitation code": "Пожалуйста, зарегистрируйтесь, используя имя пользователя, соответствующее коду приглашения",
|
||||||
"Session outdated, please login again": "Сессия устарела, пожалуйста, войдите снова",
|
"Session outdated, please login again": "Сессия устарела, пожалуйста, войдите снова",
|
||||||
"The invitation code has already been used": "The invitation code has already been used",
|
"The invitation code has already been used": "The invitation code has already been used",
|
||||||
"The user is forbidden to sign in, please contact the administrator": "Пользователю запрещен вход, пожалуйста, обратитесь к администратору",
|
"The user is forbidden to sign in, please contact the administrator": "Пользователю запрещен вход, пожалуйста, обратитесь к администратору",
|
||||||
"The user: %s doesn't exist in LDAP server": "Пользователь %s не существует на LDAP сервере",
|
"The user: %s doesn't exist in LDAP server": "Пользователь %s не существует на LDAP сервере",
|
||||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "Имя пользователя может состоять только из буквенно-цифровых символов, нижних подчеркиваний или дефисов, не может содержать последовательные дефисы или подчеркивания, а также не может начинаться или заканчиваться на дефис или подчеркивание.",
|
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "Имя пользователя может состоять только из буквенно-цифровых символов, нижних подчеркиваний или дефисов, не может содержать последовательные дефисы или подчеркивания, а также не может начинаться или заканчиваться на дефис или подчеркивание.",
|
||||||
"The value \\\"%s\\\" for account field \\\"%s\\\" doesn't match the account item regex": "The value \\\"%s\\\" for account field \\\"%s\\\" doesn't match the account item regex",
|
"The value \\\"%s\\\" for account field \\\"%s\\\" doesn't match the account item regex": "Значение \\\"%s\\\" для поля аккаунта \\\"%s\\\" не соответствует регулярному значению",
|
||||||
"The value \\\"%s\\\" for signup field \\\"%s\\\" doesn't match the signup item regex of the application \\\"%s\\\"": "The value \\\"%s\\\" for signup field \\\"%s\\\" doesn't match the signup item regex of the application \\\"%s\\\"",
|
"The value \\\"%s\\\" for signup field \\\"%s\\\" doesn't match the signup item regex of the application \\\"%s\\\"": "Значение \\\"%s\\\" поля регистрации \\\"%s\\\" не соответствует регулярному выражению приложения \\\"%s\\\"",
|
||||||
"Username already exists": "Имя пользователя уже существует",
|
"Username already exists": "Имя пользователя уже существует",
|
||||||
"Username cannot be an email address": "Имя пользователя не может быть адресом электронной почты",
|
"Username cannot be an email address": "Имя пользователя не может быть адресом электронной почты",
|
||||||
"Username cannot contain white spaces": "Имя пользователя не может содержать пробелы",
|
"Username cannot contain white spaces": "Имя пользователя не может содержать пробелы",
|
||||||
@ -78,11 +78,11 @@
|
|||||||
"general": {
|
"general": {
|
||||||
"Missing parameter": "Отсутствующий параметр",
|
"Missing parameter": "Отсутствующий параметр",
|
||||||
"Please login first": "Пожалуйста, сначала войдите в систему",
|
"Please login first": "Пожалуйста, сначала войдите в систему",
|
||||||
"The organization: %s should have one application at least": "The organization: %s should have one application at least",
|
"The organization: %s should have one application at least": "Организация: %s должна иметь хотя бы одно приложение",
|
||||||
"The user: %s doesn't exist": "Пользователь %s не существует",
|
"The user: %s doesn't exist": "Пользователь %s не существует",
|
||||||
"don't support captchaProvider: ": "неподдерживаемый captchaProvider: ",
|
"don't support captchaProvider: ": "неподдерживаемый captchaProvider: ",
|
||||||
"this operation is not allowed in demo mode": "эта операция не разрешена в демо-режиме",
|
"this operation is not allowed in demo mode": "эта операция не разрешена в демо-режиме",
|
||||||
"this operation requires administrator to perform": "this operation requires administrator to perform"
|
"this operation requires administrator to perform": "для выполнения этой операции требуется администратор"
|
||||||
},
|
},
|
||||||
"ldap": {
|
"ldap": {
|
||||||
"Ldap server exist": "LDAP-сервер существует"
|
"Ldap server exist": "LDAP-сервер существует"
|
||||||
@ -101,11 +101,11 @@
|
|||||||
"Unknown modify rule %s.": "Неизвестное изменение правила %s."
|
"Unknown modify rule %s.": "Неизвестное изменение правила %s."
|
||||||
},
|
},
|
||||||
"permission": {
|
"permission": {
|
||||||
"The permission: \\\"%s\\\" doesn't exist": "The permission: \\\"%s\\\" doesn't exist"
|
"The permission: \\\"%s\\\" doesn't exist": "Разрешение: \\\"%s\\\" не существует"
|
||||||
},
|
},
|
||||||
"provider": {
|
"provider": {
|
||||||
"Invalid application id": "Неверный идентификатор приложения",
|
"Invalid application id": "Неверный идентификатор приложения",
|
||||||
"the provider: %s does not exist": "провайдер: %s не существует"
|
"the provider: %s does not exist": "Провайдер: %s не существует"
|
||||||
},
|
},
|
||||||
"resource": {
|
"resource": {
|
||||||
"User is nil for tag: avatar": "Пользователь равен нулю для тега: аватар",
|
"User is nil for tag: avatar": "Пользователь равен нулю для тега: аватар",
|
||||||
@ -115,7 +115,7 @@
|
|||||||
"Application %s not found": "Приложение %s не найдено"
|
"Application %s not found": "Приложение %s не найдено"
|
||||||
},
|
},
|
||||||
"saml_sp": {
|
"saml_sp": {
|
||||||
"provider %s's category is not SAML": "категория провайдера %s не является SAML"
|
"provider %s's category is not SAML": "Категория провайдера %s не является SAML"
|
||||||
},
|
},
|
||||||
"service": {
|
"service": {
|
||||||
"Empty parameters for emailForm: %v": "Пустые параметры для emailForm: %v",
|
"Empty parameters for emailForm: %v": "Пустые параметры для emailForm: %v",
|
||||||
@ -148,7 +148,7 @@
|
|||||||
"verification": {
|
"verification": {
|
||||||
"Invalid captcha provider.": "Недействительный поставщик CAPTCHA.",
|
"Invalid captcha provider.": "Недействительный поставщик CAPTCHA.",
|
||||||
"Phone number is invalid in your region %s": "Номер телефона недействителен в вашем регионе %s",
|
"Phone number is invalid in your region %s": "Номер телефона недействителен в вашем регионе %s",
|
||||||
"The verification code has not been sent yet!": "The verification code has not been sent yet!",
|
"The verification code has not been sent yet!": "Код проверки еще не отправлен!",
|
||||||
"The verification code has not been sent yet, or has already been used!": "The verification code has not been sent yet, or has already been used!",
|
"The verification code has not been sent yet, or has already been used!": "The verification code has not been sent yet, or has already been used!",
|
||||||
"Turing test failed.": "Тест Тьюринга не удался.",
|
"Turing test failed.": "Тест Тьюринга не удался.",
|
||||||
"Unable to get the email modify rule.": "Невозможно получить правило изменения электронной почты.",
|
"Unable to get the email modify rule.": "Невозможно получить правило изменения электронной почты.",
|
||||||
@ -156,8 +156,8 @@
|
|||||||
"Unknown type": "Неизвестный тип",
|
"Unknown type": "Неизвестный тип",
|
||||||
"Wrong verification code!": "Неправильный код подтверждения!",
|
"Wrong verification code!": "Неправильный код подтверждения!",
|
||||||
"You should verify your code in %d min!": "Вы должны проверить свой код через %d минут!",
|
"You should verify your code in %d min!": "Вы должны проверить свой код через %d минут!",
|
||||||
"please add a SMS provider to the \\\"Providers\\\" list for the application: %s": "please add a SMS provider to the \\\"Providers\\\" list for the application: %s",
|
"please add a SMS provider to the \\\"Providers\\\" list for the application: %s": "Пожалуйста, добавьте поставщика SMS в список \\\"Провайдеры\\\" для приложения: %s",
|
||||||
"please add an Email provider to the \\\"Providers\\\" list for the application: %s": "please add an Email provider to the \\\"Providers\\\" list for the application: %s",
|
"please add an Email provider to the \\\"Providers\\\" list for the application: %s": "Пожалуйста, добавьте поставщика электронной почты в список \\\"Провайдеры\\\" для приложения: %s",
|
||||||
"the user does not exist, please sign up first": "Пользователь не существует, пожалуйста, сначала зарегистрируйтесь"
|
"the user does not exist, please sign up first": "Пользователь не существует, пожалуйста, сначала зарегистрируйтесь"
|
||||||
},
|
},
|
||||||
"webauthn": {
|
"webauthn": {
|
||||||
|
@ -200,7 +200,7 @@ func (idp *AlipayIdProvider) postWithBody(body interface{}, targetUrl string) ([
|
|||||||
|
|
||||||
formData.Set("sign", sign)
|
formData.Set("sign", sign)
|
||||||
|
|
||||||
resp, err := idp.Client.PostForm(targetUrl, formData)
|
resp, err := idp.Client.Post(targetUrl, "application/x-www-form-urlencoded;charset=utf-8", strings.NewReader(formData.Encode()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -15,33 +15,81 @@
|
|||||||
package ldap
|
package ldap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash/fnv"
|
"hash/fnv"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/conf"
|
"github.com/casdoor/casdoor/conf"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
ldap "github.com/forestmgy/ldapserver"
|
ldap "github.com/casdoor/ldapserver"
|
||||||
"github.com/lor00x/goldap/message"
|
"github.com/lor00x/goldap/message"
|
||||||
)
|
)
|
||||||
|
|
||||||
func StartLdapServer() {
|
func StartLdapServer() {
|
||||||
ldapServerPort := conf.GetConfigString("ldapServerPort")
|
ldapServerPort := conf.GetConfigString("ldapServerPort")
|
||||||
if ldapServerPort == "" || ldapServerPort == "0" {
|
ldapsServerPort := conf.GetConfigString("ldapsServerPort")
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
server := ldap.NewServer()
|
server := ldap.NewServer()
|
||||||
|
serverSsl := ldap.NewServer()
|
||||||
routes := ldap.NewRouteMux()
|
routes := ldap.NewRouteMux()
|
||||||
|
|
||||||
routes.Bind(handleBind)
|
routes.Bind(handleBind)
|
||||||
routes.Search(handleSearch).Label(" SEARCH****")
|
routes.Search(handleSearch).Label(" SEARCH****")
|
||||||
|
|
||||||
server.Handle(routes)
|
server.Handle(routes)
|
||||||
err := server.ListenAndServe("0.0.0.0:" + ldapServerPort)
|
serverSsl.Handle(routes)
|
||||||
|
go func() {
|
||||||
|
if ldapServerPort == "" || ldapServerPort == "0" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := server.ListenAndServe("0.0.0.0:" + ldapServerPort)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("StartLdapServer() failed, err = %s", err.Error())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if ldapsServerPort == "" || ldapsServerPort == "0" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ldapsCertId := conf.GetConfigString("ldapsCertId")
|
||||||
|
if ldapsCertId == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
config, err := getTLSconfig(ldapsCertId)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("StartLdapsServer() failed, err = %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
secureConn := func(s *ldap.Server) {
|
||||||
|
s.Listener = tls.NewListener(s.Listener, config)
|
||||||
|
}
|
||||||
|
err = serverSsl.ListenAndServe("0.0.0.0:"+ldapsServerPort, secureConn)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("StartLdapsServer() failed, err = %s", err.Error())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTLSconfig(ldapsCertId string) (*tls.Config, error) {
|
||||||
|
rawCert, err := object.GetCert(ldapsCertId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("StartLdapServer() failed, err = %s", err.Error())
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if rawCert == nil {
|
||||||
|
return nil, fmt.Errorf("cert is empty")
|
||||||
|
}
|
||||||
|
cert, err := tls.X509KeyPair([]byte(rawCert.Certificate), []byte(rawCert.PrivateKey))
|
||||||
|
if err != nil {
|
||||||
|
return &tls.Config{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tls.Config{
|
||||||
|
MinVersion: tls.VersionTLS10,
|
||||||
|
MaxVersion: tls.VersionTLS13,
|
||||||
|
Certificates: []tls.Certificate{cert},
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleBind(w ldap.ResponseWriter, m *ldap.Message) {
|
func handleBind(w ldap.ResponseWriter, m *ldap.Message) {
|
||||||
@ -142,7 +190,7 @@ func handleSearch(w ldap.ResponseWriter, m *ldap.Message) {
|
|||||||
}
|
}
|
||||||
for _, attr := range attrs {
|
for _, attr := range attrs {
|
||||||
e.AddAttribute(message.AttributeDescription(attr), getAttribute(string(attr), user))
|
e.AddAttribute(message.AttributeDescription(attr), getAttribute(string(attr), user))
|
||||||
if string(attr) == "cn" {
|
if string(attr) == "title" {
|
||||||
e.AddAttribute(message.AttributeDescription(attr), getAttribute("title", user))
|
e.AddAttribute(message.AttributeDescription(attr), getAttribute("title", user))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ import (
|
|||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"github.com/lor00x/goldap/message"
|
"github.com/lor00x/goldap/message"
|
||||||
|
|
||||||
ldap "github.com/forestmgy/ldapserver"
|
ldap "github.com/casdoor/ldapserver"
|
||||||
|
|
||||||
"github.com/xorm-io/builder"
|
"github.com/xorm-io/builder"
|
||||||
)
|
)
|
||||||
|
2
main.go
2
main.go
@ -56,6 +56,7 @@ func main() {
|
|||||||
beego.InsertFilter("*", beego.BeforeRouter, routers.StaticFilter)
|
beego.InsertFilter("*", beego.BeforeRouter, routers.StaticFilter)
|
||||||
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.TimeoutFilter)
|
||||||
beego.InsertFilter("*", beego.BeforeRouter, routers.ApiFilter)
|
beego.InsertFilter("*", beego.BeforeRouter, routers.ApiFilter)
|
||||||
beego.InsertFilter("*", beego.BeforeRouter, routers.PrometheusFilter)
|
beego.InsertFilter("*", beego.BeforeRouter, routers.PrometheusFilter)
|
||||||
beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage)
|
beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage)
|
||||||
@ -71,6 +72,7 @@ func main() {
|
|||||||
beego.BConfig.WebConfig.Session.SessionProviderConfig = conf.GetConfigString("redisEndpoint")
|
beego.BConfig.WebConfig.Session.SessionProviderConfig = conf.GetConfigString("redisEndpoint")
|
||||||
}
|
}
|
||||||
beego.BConfig.WebConfig.Session.SessionCookieLifeTime = 3600 * 24 * 30
|
beego.BConfig.WebConfig.Session.SessionCookieLifeTime = 3600 * 24 * 30
|
||||||
|
beego.BConfig.WebConfig.Session.SessionGCMaxLifetime = 3600 * 24 * 30
|
||||||
// beego.BConfig.WebConfig.Session.SessionCookieSameSite = http.SameSiteNoneMode
|
// beego.BConfig.WebConfig.Session.SessionCookieSameSite = http.SameSiteNoneMode
|
||||||
|
|
||||||
err := logs.SetLogger(logs.AdapterFile, conf.GetConfigString("logConfig"))
|
err := logs.SetLogger(logs.AdapterFile, conf.GetConfigString("logConfig"))
|
||||||
|
@ -31,15 +31,17 @@ type SigninMethod struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SignupItem struct {
|
type SignupItem struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Visible bool `json:"visible"`
|
Visible bool `json:"visible"`
|
||||||
Required bool `json:"required"`
|
Required bool `json:"required"`
|
||||||
Prompted bool `json:"prompted"`
|
Prompted bool `json:"prompted"`
|
||||||
CustomCss string `json:"customCss"`
|
Type string `json:"type"`
|
||||||
Label string `json:"label"`
|
CustomCss string `json:"customCss"`
|
||||||
Placeholder string `json:"placeholder"`
|
Label string `json:"label"`
|
||||||
Regex string `json:"regex"`
|
Placeholder string `json:"placeholder"`
|
||||||
Rule string `json:"rule"`
|
Options []string `json:"options"`
|
||||||
|
Regex string `json:"regex"`
|
||||||
|
Rule string `json:"rule"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SigninItem struct {
|
type SigninItem struct {
|
||||||
@ -78,24 +80,28 @@ type Application struct {
|
|||||||
EnableSamlCompress bool `json:"enableSamlCompress"`
|
EnableSamlCompress bool `json:"enableSamlCompress"`
|
||||||
EnableSamlC14n10 bool `json:"enableSamlC14n10"`
|
EnableSamlC14n10 bool `json:"enableSamlC14n10"`
|
||||||
EnableSamlPostBinding bool `json:"enableSamlPostBinding"`
|
EnableSamlPostBinding bool `json:"enableSamlPostBinding"`
|
||||||
|
UseEmailAsSamlNameId bool `json:"useEmailAsSamlNameId"`
|
||||||
EnableWebAuthn bool `json:"enableWebAuthn"`
|
EnableWebAuthn bool `json:"enableWebAuthn"`
|
||||||
EnableLinkWithEmail bool `json:"enableLinkWithEmail"`
|
EnableLinkWithEmail bool `json:"enableLinkWithEmail"`
|
||||||
OrgChoiceMode string `json:"orgChoiceMode"`
|
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"`
|
||||||
SigninMethods []*SigninMethod `xorm:"varchar(2000)" json:"signinMethods"`
|
SigninMethods []*SigninMethod `xorm:"varchar(2000)" json:"signinMethods"`
|
||||||
SignupItems []*SignupItem `xorm:"varchar(2000)" json:"signupItems"`
|
SignupItems []*SignupItem `xorm:"varchar(3000)" json:"signupItems"`
|
||||||
SigninItems []*SigninItem `xorm:"mediumtext" json:"signinItems"`
|
SigninItems []*SigninItem `xorm:"mediumtext" json:"signinItems"`
|
||||||
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
|
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
|
||||||
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
|
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
|
||||||
CertPublicKey string `xorm:"-" json:"certPublicKey"`
|
CertPublicKey string `xorm:"-" json:"certPublicKey"`
|
||||||
Tags []string `xorm:"mediumtext" json:"tags"`
|
Tags []string `xorm:"mediumtext" json:"tags"`
|
||||||
SamlAttributes []*SamlItem `xorm:"varchar(1000)" json:"samlAttributes"`
|
SamlAttributes []*SamlItem `xorm:"varchar(1000)" json:"samlAttributes"`
|
||||||
|
IsShared bool `json:"isShared"`
|
||||||
|
IpRestriction string `json:"ipRestriction"`
|
||||||
|
|
||||||
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
||||||
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
|
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
|
||||||
RedirectUris []string `xorm:"varchar(1000)" json:"redirectUris"`
|
RedirectUris []string `xorm:"varchar(1000)" json:"redirectUris"`
|
||||||
TokenFormat string `xorm:"varchar(100)" json:"tokenFormat"`
|
TokenFormat string `xorm:"varchar(100)" json:"tokenFormat"`
|
||||||
|
TokenSigningMethod string `xorm:"varchar(100)" json:"tokenSigningMethod"`
|
||||||
TokenFields []string `xorm:"varchar(1000)" json:"tokenFields"`
|
TokenFields []string `xorm:"varchar(1000)" json:"tokenFields"`
|
||||||
ExpireInHours int `json:"expireInHours"`
|
ExpireInHours int `json:"expireInHours"`
|
||||||
RefreshExpireInHours int `json:"refreshExpireInHours"`
|
RefreshExpireInHours int `json:"refreshExpireInHours"`
|
||||||
@ -103,6 +109,7 @@ type Application struct {
|
|||||||
SigninUrl string `xorm:"varchar(200)" json:"signinUrl"`
|
SigninUrl string `xorm:"varchar(200)" json:"signinUrl"`
|
||||||
ForgetUrl string `xorm:"varchar(200)" json:"forgetUrl"`
|
ForgetUrl string `xorm:"varchar(200)" json:"forgetUrl"`
|
||||||
AffiliationUrl string `xorm:"varchar(100)" json:"affiliationUrl"`
|
AffiliationUrl string `xorm:"varchar(100)" json:"affiliationUrl"`
|
||||||
|
IpWhitelist string `xorm:"varchar(200)" json:"ipWhitelist"`
|
||||||
TermsOfUse string `xorm:"varchar(100)" json:"termsOfUse"`
|
TermsOfUse string `xorm:"varchar(100)" json:"termsOfUse"`
|
||||||
SignupHtml string `xorm:"mediumtext" json:"signupHtml"`
|
SignupHtml string `xorm:"mediumtext" json:"signupHtml"`
|
||||||
SigninHtml string `xorm:"mediumtext" json:"signinHtml"`
|
SigninHtml string `xorm:"mediumtext" json:"signinHtml"`
|
||||||
@ -123,9 +130,9 @@ func GetApplicationCount(owner, field, value string) (int64, error) {
|
|||||||
return session.Count(&Application{})
|
return session.Count(&Application{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetOrganizationApplicationCount(owner, Organization, field, value string) (int64, error) {
|
func GetOrganizationApplicationCount(owner, organization, field, value string) (int64, error) {
|
||||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||||
return session.Count(&Application{Organization: Organization})
|
return session.Where("organization = ? or is_shared = ? ", organization, true).Count(&Application{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetApplications(owner string) ([]*Application, error) {
|
func GetApplications(owner string) ([]*Application, error) {
|
||||||
@ -140,7 +147,7 @@ func GetApplications(owner string) ([]*Application, error) {
|
|||||||
|
|
||||||
func GetOrganizationApplications(owner string, organization string) ([]*Application, error) {
|
func GetOrganizationApplications(owner string, organization string) ([]*Application, error) {
|
||||||
applications := []*Application{}
|
applications := []*Application{}
|
||||||
err := ormer.Engine.Desc("created_time").Find(&applications, &Application{Organization: organization})
|
err := ormer.Engine.Desc("created_time").Where("organization = ? or is_shared = ? ", organization, true).Find(&applications, &Application{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return applications, err
|
return applications, err
|
||||||
}
|
}
|
||||||
@ -162,7 +169,7 @@ func GetPaginationApplications(owner string, offset, limit int, field, value, so
|
|||||||
func GetPaginationOrganizationApplications(owner, organization string, offset, limit int, field, value, sortField, sortOrder string) ([]*Application, error) {
|
func GetPaginationOrganizationApplications(owner, organization string, offset, limit int, field, value, sortField, sortOrder string) ([]*Application, error) {
|
||||||
applications := []*Application{}
|
applications := []*Application{}
|
||||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||||
err := session.Find(&applications, &Application{Organization: organization})
|
err := session.Where("organization = ? or is_shared = ? ", organization, true).Find(&applications, &Application{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return applications, err
|
return applications, err
|
||||||
}
|
}
|
||||||
@ -337,12 +344,18 @@ func getApplication(owner string, name string) (*Application, error) {
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
application := Application{Owner: owner, Name: name}
|
realApplicationName, sharedOrg := util.GetSharedOrgFromApp(name)
|
||||||
|
|
||||||
|
application := Application{Owner: owner, Name: realApplicationName}
|
||||||
existed, err := ormer.Engine.Get(&application)
|
existed, err := ormer.Engine.Get(&application)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if application.IsShared && sharedOrg != "" {
|
||||||
|
application.Organization = sharedOrg
|
||||||
|
}
|
||||||
|
|
||||||
if existed {
|
if existed {
|
||||||
err = extendApplicationWithProviders(&application)
|
err = extendApplicationWithProviders(&application)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -428,11 +441,18 @@ func GetApplicationByUserId(userId string) (application *Application, err error)
|
|||||||
|
|
||||||
func GetApplicationByClientId(clientId string) (*Application, error) {
|
func GetApplicationByClientId(clientId string) (*Application, error) {
|
||||||
application := Application{}
|
application := Application{}
|
||||||
existed, err := ormer.Engine.Where("client_id=?", clientId).Get(&application)
|
|
||||||
|
realClientId, sharedOrg := util.GetSharedOrgFromApp(clientId)
|
||||||
|
|
||||||
|
existed, err := ormer.Engine.Where("client_id=?", realClientId).Get(&application)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if application.IsShared && sharedOrg != "" {
|
||||||
|
application.Organization = sharedOrg
|
||||||
|
}
|
||||||
|
|
||||||
if existed {
|
if existed {
|
||||||
err = extendApplicationWithProviders(&application)
|
err = extendApplicationWithProviders(&application)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -516,7 +536,7 @@ func GetMaskedApplication(application *Application, userId string) *Application
|
|||||||
|
|
||||||
providerItems := []*ProviderItem{}
|
providerItems := []*ProviderItem{}
|
||||||
for _, providerItem := range application.Providers {
|
for _, providerItem := range application.Providers {
|
||||||
if providerItem.Provider != nil && (providerItem.Provider.Category == "OAuth" || providerItem.Provider.Category == "Web3" || providerItem.Provider.Category == "Captcha") {
|
if providerItem.Provider != nil && (providerItem.Provider.Category == "OAuth" || providerItem.Provider.Category == "Web3" || providerItem.Provider.Category == "Captcha" || providerItem.Provider.Category == "SAML") {
|
||||||
providerItems = append(providerItems, providerItem)
|
providerItems = append(providerItems, providerItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -626,6 +646,10 @@ func UpdateApplication(id string, application *Application) (bool, error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if application.IsShared == true && application.Organization != "built-in" {
|
||||||
|
return false, fmt.Errorf("only applications belonging to built-in organization can be shared")
|
||||||
|
}
|
||||||
|
|
||||||
for _, providerItem := range application.Providers {
|
for _, providerItem := range application.Providers {
|
||||||
providerItem.Provider = nil
|
providerItem.Provider = nil
|
||||||
}
|
}
|
||||||
@ -699,8 +723,15 @@ func (application *Application) GetId() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (application *Application) IsRedirectUriValid(redirectUri string) bool {
|
func (application *Application) IsRedirectUriValid(redirectUri string) bool {
|
||||||
redirectUris := append([]string{"http://localhost:", "https://localhost:", "http://127.0.0.1:", "http://casdoor-app", ".chromiumapp.org"}, application.RedirectUris...)
|
isValid, err := util.IsValidOrigin(redirectUri)
|
||||||
for _, targetUri := range redirectUris {
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if isValid {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, targetUri := range application.RedirectUris {
|
||||||
targetUriRegex := regexp.MustCompile(targetUri)
|
targetUriRegex := regexp.MustCompile(targetUri)
|
||||||
if targetUriRegex.MatchString(redirectUri) || strings.Contains(redirectUri, targetUri) {
|
if targetUriRegex.MatchString(redirectUri) || strings.Contains(redirectUri, targetUri) {
|
||||||
return true
|
return true
|
||||||
|
@ -381,7 +381,13 @@ func CheckUserPassword(organization string, username string, password string, la
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = checkPasswordExpired(user, lang)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -520,11 +526,46 @@ func CheckUsername(username string, lang string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CheckUsernameWithEmail(username string, lang string) string {
|
||||||
|
if username == "" {
|
||||||
|
return i18n.Translate(lang, "check:Empty username.")
|
||||||
|
} else if len(username) > 39 {
|
||||||
|
return i18n.Translate(lang, "check:Username is too long (maximum is 39 characters).")
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://stackoverflow.com/questions/58726546/github-username-convention-using-regex
|
||||||
|
|
||||||
|
if !util.ReUserNameWithEmail.MatchString(username) {
|
||||||
|
return i18n.Translate(lang, "check:Username supports email format. Also The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline. Also pay attention to the email format.")
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func CheckUpdateUser(oldUser, user *User, lang string) string {
|
func CheckUpdateUser(oldUser, user *User, lang string) string {
|
||||||
if oldUser.Name != user.Name {
|
if oldUser.Name != user.Name {
|
||||||
if msg := CheckUsername(user.Name, lang); msg != "" {
|
organizationName := oldUser.Owner
|
||||||
return msg
|
if organizationName == "" {
|
||||||
|
organizationName = user.Owner
|
||||||
}
|
}
|
||||||
|
|
||||||
|
organization, err := getOrganization("admin", organizationName)
|
||||||
|
if err != nil {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
if organization == nil {
|
||||||
|
return fmt.Sprintf(i18n.Translate(lang, "auth:The organization: %s does not exist"), organizationName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if organization.UseEmailAsUsername {
|
||||||
|
if msg := CheckUsernameWithEmail(user.Name, lang); msg != "" {
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if msg := CheckUsername(user.Name, lang); msg != "" {
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if HasUserByField(user.Owner, "name", user.Name) {
|
if HasUserByField(user.Owner, "name", user.Name) {
|
||||||
return i18n.Translate(lang, "check:Username already exists")
|
return i18n.Translate(lang, "check:Username already exists")
|
||||||
}
|
}
|
||||||
@ -539,6 +580,11 @@ func CheckUpdateUser(oldUser, user *User, lang string) string {
|
|||||||
return i18n.Translate(lang, "check:Phone already exists")
|
return i18n.Translate(lang, "check:Phone already exists")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if oldUser.IpWhitelist != user.IpWhitelist {
|
||||||
|
if err := CheckIpWhitelist(user.IpWhitelist, lang); err != nil {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
104
object/check_ip.go
Normal file
104
object/check_ip.go
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
// Copyright 2024 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"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/i18n"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CheckEntryIp(clientIp string, user *User, application *Application, organization *Organization, lang string) error {
|
||||||
|
entryIp := net.ParseIP(clientIp)
|
||||||
|
if entryIp == nil {
|
||||||
|
return fmt.Errorf(i18n.Translate(lang, "check:Failed to parse client IP: %s"), clientIp)
|
||||||
|
} else if entryIp.IsLoopback() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if user != nil {
|
||||||
|
err = isEntryIpAllowd(user.IpWhitelist, entryIp, lang)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(err.Error() + user.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if application != nil {
|
||||||
|
err = isEntryIpAllowd(application.IpWhitelist, entryIp, lang)
|
||||||
|
if err != nil {
|
||||||
|
application.IpRestriction = err.Error() + application.Name
|
||||||
|
return fmt.Errorf(err.Error() + application.Name)
|
||||||
|
} else {
|
||||||
|
application.IpRestriction = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if organization == nil && application.OrganizationObj != nil {
|
||||||
|
organization = application.OrganizationObj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if organization != nil {
|
||||||
|
err = isEntryIpAllowd(organization.IpWhitelist, entryIp, lang)
|
||||||
|
if err != nil {
|
||||||
|
organization.IpRestriction = err.Error() + organization.Name
|
||||||
|
return fmt.Errorf(err.Error() + organization.Name)
|
||||||
|
} else {
|
||||||
|
organization.IpRestriction = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isEntryIpAllowd(ipWhitelistStr string, entryIp net.IP, lang string) error {
|
||||||
|
if ipWhitelistStr == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ipWhitelist := strings.Split(ipWhitelistStr, ",")
|
||||||
|
for _, ip := range ipWhitelist {
|
||||||
|
_, ipNet, err := net.ParseCIDR(ip)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if ipNet == nil {
|
||||||
|
return fmt.Errorf(i18n.Translate(lang, "check:CIDR for IP: %s should not be empty"), entryIp.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if ipNet.Contains(entryIp) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf(i18n.Translate(lang, "check:Your IP address: %s has been banned according to the configuration of: "), entryIp.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckIpWhitelist(ipWhitelistStr string, lang string) error {
|
||||||
|
if ipWhitelistStr == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ipWhiteList := strings.Split(ipWhitelistStr, ",")
|
||||||
|
for _, ip := range ipWhiteList {
|
||||||
|
if _, _, err := net.ParseCIDR(ip); err != nil {
|
||||||
|
return fmt.Errorf(i18n.Translate(lang, "check:%s does not meet the CIDR format requirements: %s"), ip, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
53
object/check_password_expired.go
Normal file
53
object/check_password_expired.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
// Copyright 2024 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/i18n"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func checkPasswordExpired(user *User, lang string) error {
|
||||||
|
organization, err := GetOrganizationByUser(user)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if organization == nil {
|
||||||
|
return fmt.Errorf(i18n.Translate(lang, "check:Organization does not exist"))
|
||||||
|
}
|
||||||
|
|
||||||
|
passwordExpireDays := organization.PasswordExpireDays
|
||||||
|
if passwordExpireDays <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
lastChangePasswordTime := user.LastChangePasswordTime
|
||||||
|
if lastChangePasswordTime == "" {
|
||||||
|
if user.CreatedTime == "" {
|
||||||
|
return fmt.Errorf(i18n.Translate(lang, "check:Your password has expired. Please reset your password by clicking \"Forgot password\""))
|
||||||
|
}
|
||||||
|
lastChangePasswordTime = user.CreatedTime
|
||||||
|
}
|
||||||
|
|
||||||
|
lastTime := util.String2Time(lastChangePasswordTime)
|
||||||
|
expireTime := lastTime.AddDate(0, 0, passwordExpireDays)
|
||||||
|
if time.Now().After(expireTime) {
|
||||||
|
return fmt.Errorf(i18n.Translate(lang, "check:Your password has expired. Please reset your password by clicking \"Forgot password\""))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -52,6 +52,9 @@ func GetFailedSigninConfigByUser(user *User) (int, int, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, err
|
return 0, 0, err
|
||||||
}
|
}
|
||||||
|
if application == nil {
|
||||||
|
return 0, 0, fmt.Errorf("the application for user %s is not found", user.GetId())
|
||||||
|
}
|
||||||
|
|
||||||
failedSigninLimit := application.FailedSigninLimit
|
failedSigninLimit := application.FailedSigninLimit
|
||||||
if failedSigninLimit == 0 {
|
if failedSigninLimit == 0 {
|
||||||
|
@ -25,6 +25,12 @@ type Dashboard struct {
|
|||||||
ProviderCounts []int `json:"providerCounts"`
|
ProviderCounts []int `json:"providerCounts"`
|
||||||
ApplicationCounts []int `json:"applicationCounts"`
|
ApplicationCounts []int `json:"applicationCounts"`
|
||||||
SubscriptionCounts []int `json:"subscriptionCounts"`
|
SubscriptionCounts []int `json:"subscriptionCounts"`
|
||||||
|
RoleCounts []int `json:"roleCounts"`
|
||||||
|
GroupCounts []int `json:"groupCounts"`
|
||||||
|
ResourceCounts []int `json:"resourceCounts"`
|
||||||
|
CertCounts []int `json:"certCounts"`
|
||||||
|
PermissionCounts []int `json:"permissionCounts"`
|
||||||
|
TransactionCounts []int `json:"transactionCounts"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDashboard(owner string) (*Dashboard, error) {
|
func GetDashboard(owner string) (*Dashboard, error) {
|
||||||
@ -38,6 +44,12 @@ func GetDashboard(owner string) (*Dashboard, error) {
|
|||||||
ProviderCounts: make([]int, 31),
|
ProviderCounts: make([]int, 31),
|
||||||
ApplicationCounts: make([]int, 31),
|
ApplicationCounts: make([]int, 31),
|
||||||
SubscriptionCounts: make([]int, 31),
|
SubscriptionCounts: make([]int, 31),
|
||||||
|
RoleCounts: make([]int, 31),
|
||||||
|
GroupCounts: make([]int, 31),
|
||||||
|
ResourceCounts: make([]int, 31),
|
||||||
|
CertCounts: make([]int, 31),
|
||||||
|
PermissionCounts: make([]int, 31),
|
||||||
|
TransactionCounts: make([]int, 31),
|
||||||
}
|
}
|
||||||
|
|
||||||
organizations := []Organization{}
|
organizations := []Organization{}
|
||||||
@ -45,9 +57,15 @@ func GetDashboard(owner string) (*Dashboard, error) {
|
|||||||
providers := []Provider{}
|
providers := []Provider{}
|
||||||
applications := []Application{}
|
applications := []Application{}
|
||||||
subscriptions := []Subscription{}
|
subscriptions := []Subscription{}
|
||||||
|
roles := []Role{}
|
||||||
|
groups := []Group{}
|
||||||
|
resources := []Resource{}
|
||||||
|
certs := []Cert{}
|
||||||
|
permissions := []Permission{}
|
||||||
|
transactions := []Transaction{}
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(5)
|
wg.Add(11)
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
if err := ormer.Engine.Find(&organizations, &Organization{Owner: owner}); err != nil {
|
if err := ormer.Engine.Find(&organizations, &Organization{Owner: owner}); err != nil {
|
||||||
@ -86,6 +104,50 @@ func GetDashboard(owner string) (*Dashboard, error) {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
if err := ormer.Engine.Find(&roles, &Role{Owner: owner}); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
if err := ormer.Engine.Find(&groups, &Group{Owner: owner}); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
if err := ormer.Engine.Find(&resources, &Resource{Owner: owner}); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
if err := ormer.Engine.Find(&certs, &Cert{Owner: owner}); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
if err := ormer.Engine.Find(&permissions, &Permission{Owner: owner}); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
if err := ormer.Engine.Find(&transactions, &Transaction{Owner: owner}); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
nowTime := time.Now()
|
nowTime := time.Now()
|
||||||
@ -96,6 +158,12 @@ func GetDashboard(owner string) (*Dashboard, error) {
|
|||||||
dashboard.ProviderCounts[30-i] = countCreatedBefore(providers, cutTime)
|
dashboard.ProviderCounts[30-i] = countCreatedBefore(providers, cutTime)
|
||||||
dashboard.ApplicationCounts[30-i] = countCreatedBefore(applications, cutTime)
|
dashboard.ApplicationCounts[30-i] = countCreatedBefore(applications, cutTime)
|
||||||
dashboard.SubscriptionCounts[30-i] = countCreatedBefore(subscriptions, cutTime)
|
dashboard.SubscriptionCounts[30-i] = countCreatedBefore(subscriptions, cutTime)
|
||||||
|
dashboard.RoleCounts[30-i] = countCreatedBefore(roles, cutTime)
|
||||||
|
dashboard.GroupCounts[30-i] = countCreatedBefore(groups, cutTime)
|
||||||
|
dashboard.ResourceCounts[30-i] = countCreatedBefore(resources, cutTime)
|
||||||
|
dashboard.CertCounts[30-i] = countCreatedBefore(certs, cutTime)
|
||||||
|
dashboard.PermissionCounts[30-i] = countCreatedBefore(permissions, cutTime)
|
||||||
|
dashboard.TransactionCounts[30-i] = countCreatedBefore(transactions, cutTime)
|
||||||
}
|
}
|
||||||
return dashboard, nil
|
return dashboard, nil
|
||||||
}
|
}
|
||||||
@ -138,6 +206,48 @@ func countCreatedBefore(objects interface{}, before time.Time) int {
|
|||||||
count++
|
count++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case []Role:
|
||||||
|
for _, r := range obj {
|
||||||
|
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", r.CreatedTime)
|
||||||
|
if createdTime.Before(before) {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case []Group:
|
||||||
|
for _, g := range obj {
|
||||||
|
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", g.CreatedTime)
|
||||||
|
if createdTime.Before(before) {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case []Resource:
|
||||||
|
for _, r := range obj {
|
||||||
|
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", r.CreatedTime)
|
||||||
|
if createdTime.Before(before) {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case []Cert:
|
||||||
|
for _, c := range obj {
|
||||||
|
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", c.CreatedTime)
|
||||||
|
if createdTime.Before(before) {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case []Permission:
|
||||||
|
for _, p := range obj {
|
||||||
|
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", p.CreatedTime)
|
||||||
|
if createdTime.Before(before) {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case []Transaction:
|
||||||
|
for _, t := range obj {
|
||||||
|
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", t.CreatedTime)
|
||||||
|
if createdTime.Before(before) {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return count
|
return count
|
||||||
}
|
}
|
||||||
|
@ -48,12 +48,16 @@ type InitData struct {
|
|||||||
Transactions []*Transaction `json:"transactions"`
|
Transactions []*Transaction `json:"transactions"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var initDataNewOnly bool
|
||||||
|
|
||||||
func InitFromFile() {
|
func InitFromFile() {
|
||||||
initDataFile := conf.GetConfigString("initDataFile")
|
initDataFile := conf.GetConfigString("initDataFile")
|
||||||
if initDataFile == "" {
|
if initDataFile == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initDataNewOnly = conf.GetConfigBool("initDataNewOnly")
|
||||||
|
|
||||||
initData, err := readInitDataFromFile(initDataFile)
|
initData, err := readInitDataFromFile(initDataFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -182,6 +186,9 @@ func readInitDataFromFile(filePath string) (*InitData, error) {
|
|||||||
if organization.Tags == nil {
|
if organization.Tags == nil {
|
||||||
organization.Tags = []string{}
|
organization.Tags = []string{}
|
||||||
}
|
}
|
||||||
|
if organization.AccountItems == nil {
|
||||||
|
organization.AccountItems = []*AccountItem{}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for _, application := range data.Applications {
|
for _, application := range data.Applications {
|
||||||
if application.Providers == nil {
|
if application.Providers == nil {
|
||||||
@ -266,6 +273,9 @@ func initDefinedOrganization(organization *Organization) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if existed != nil {
|
if existed != nil {
|
||||||
|
if initDataNewOnly {
|
||||||
|
return
|
||||||
|
}
|
||||||
affected, err := deleteOrganization(organization)
|
affected, err := deleteOrganization(organization)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -275,7 +285,9 @@ func initDefinedOrganization(organization *Organization) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
organization.CreatedTime = util.GetCurrentTime()
|
organization.CreatedTime = util.GetCurrentTime()
|
||||||
organization.AccountItems = getBuiltInAccountItems()
|
if len(organization.AccountItems) == 0 {
|
||||||
|
organization.AccountItems = getBuiltInAccountItems()
|
||||||
|
}
|
||||||
|
|
||||||
_, err = AddOrganization(organization)
|
_, err = AddOrganization(organization)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -290,6 +302,9 @@ func initDefinedApplication(application *Application) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if existed != nil {
|
if existed != nil {
|
||||||
|
if initDataNewOnly {
|
||||||
|
return
|
||||||
|
}
|
||||||
affected, err := deleteApplication(application)
|
affected, err := deleteApplication(application)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -311,6 +326,9 @@ func initDefinedUser(user *User) {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if existed != nil {
|
if existed != nil {
|
||||||
|
if initDataNewOnly {
|
||||||
|
return
|
||||||
|
}
|
||||||
affected, err := deleteUser(user)
|
affected, err := deleteUser(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -337,6 +355,9 @@ func initDefinedCert(cert *Cert) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if existed != nil {
|
if existed != nil {
|
||||||
|
if initDataNewOnly {
|
||||||
|
return
|
||||||
|
}
|
||||||
affected, err := DeleteCert(cert)
|
affected, err := DeleteCert(cert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -359,6 +380,9 @@ func initDefinedLdap(ldap *Ldap) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if existed != nil {
|
if existed != nil {
|
||||||
|
if initDataNewOnly {
|
||||||
|
return
|
||||||
|
}
|
||||||
affected, err := DeleteLdap(ldap)
|
affected, err := DeleteLdap(ldap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -380,6 +404,9 @@ func initDefinedProvider(provider *Provider) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if existed != nil {
|
if existed != nil {
|
||||||
|
if initDataNewOnly {
|
||||||
|
return
|
||||||
|
}
|
||||||
affected, err := DeleteProvider(provider)
|
affected, err := DeleteProvider(provider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -401,6 +428,9 @@ func initDefinedModel(model *Model) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if existed != nil {
|
if existed != nil {
|
||||||
|
if initDataNewOnly {
|
||||||
|
return
|
||||||
|
}
|
||||||
affected, err := DeleteModel(model)
|
affected, err := DeleteModel(model)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -423,6 +453,9 @@ func initDefinedPermission(permission *Permission) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if existed != nil {
|
if existed != nil {
|
||||||
|
if initDataNewOnly {
|
||||||
|
return
|
||||||
|
}
|
||||||
affected, err := deletePermission(permission)
|
affected, err := deletePermission(permission)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -445,6 +478,9 @@ func initDefinedPayment(payment *Payment) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if existed != nil {
|
if existed != nil {
|
||||||
|
if initDataNewOnly {
|
||||||
|
return
|
||||||
|
}
|
||||||
affected, err := DeletePayment(payment)
|
affected, err := DeletePayment(payment)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -467,6 +503,9 @@ func initDefinedProduct(product *Product) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if existed != nil {
|
if existed != nil {
|
||||||
|
if initDataNewOnly {
|
||||||
|
return
|
||||||
|
}
|
||||||
affected, err := DeleteProduct(product)
|
affected, err := DeleteProduct(product)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -489,6 +528,9 @@ func initDefinedResource(resource *Resource) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if existed != nil {
|
if existed != nil {
|
||||||
|
if initDataNewOnly {
|
||||||
|
return
|
||||||
|
}
|
||||||
affected, err := DeleteResource(resource)
|
affected, err := DeleteResource(resource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -511,6 +553,9 @@ func initDefinedRole(role *Role) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if existed != nil {
|
if existed != nil {
|
||||||
|
if initDataNewOnly {
|
||||||
|
return
|
||||||
|
}
|
||||||
affected, err := deleteRole(role)
|
affected, err := deleteRole(role)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -533,6 +578,9 @@ func initDefinedSyncer(syncer *Syncer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if existed != nil {
|
if existed != nil {
|
||||||
|
if initDataNewOnly {
|
||||||
|
return
|
||||||
|
}
|
||||||
affected, err := DeleteSyncer(syncer)
|
affected, err := DeleteSyncer(syncer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -555,6 +603,9 @@ func initDefinedToken(token *Token) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if existed != nil {
|
if existed != nil {
|
||||||
|
if initDataNewOnly {
|
||||||
|
return
|
||||||
|
}
|
||||||
affected, err := DeleteToken(token)
|
affected, err := DeleteToken(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -577,6 +628,9 @@ func initDefinedWebhook(webhook *Webhook) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if existed != nil {
|
if existed != nil {
|
||||||
|
if initDataNewOnly {
|
||||||
|
return
|
||||||
|
}
|
||||||
affected, err := DeleteWebhook(webhook)
|
affected, err := DeleteWebhook(webhook)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -598,6 +652,9 @@ func initDefinedGroup(group *Group) {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if existed != nil {
|
if existed != nil {
|
||||||
|
if initDataNewOnly {
|
||||||
|
return
|
||||||
|
}
|
||||||
affected, err := deleteGroup(group)
|
affected, err := deleteGroup(group)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -619,6 +676,9 @@ func initDefinedAdapter(adapter *Adapter) {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if existed != nil {
|
if existed != nil {
|
||||||
|
if initDataNewOnly {
|
||||||
|
return
|
||||||
|
}
|
||||||
affected, err := DeleteAdapter(adapter)
|
affected, err := DeleteAdapter(adapter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -640,6 +700,9 @@ func initDefinedEnforcer(enforcer *Enforcer) {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if existed != nil {
|
if existed != nil {
|
||||||
|
if initDataNewOnly {
|
||||||
|
return
|
||||||
|
}
|
||||||
affected, err := DeleteEnforcer(enforcer)
|
affected, err := DeleteEnforcer(enforcer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -661,6 +724,9 @@ func initDefinedPlan(plan *Plan) {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if existed != nil {
|
if existed != nil {
|
||||||
|
if initDataNewOnly {
|
||||||
|
return
|
||||||
|
}
|
||||||
affected, err := DeletePlan(plan)
|
affected, err := DeletePlan(plan)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -682,6 +748,9 @@ func initDefinedPricing(pricing *Pricing) {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if existed != nil {
|
if existed != nil {
|
||||||
|
if initDataNewOnly {
|
||||||
|
return
|
||||||
|
}
|
||||||
affected, err := DeletePricing(pricing)
|
affected, err := DeletePricing(pricing)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -703,6 +772,9 @@ func initDefinedInvitation(invitation *Invitation) {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if existed != nil {
|
if existed != nil {
|
||||||
|
if initDataNewOnly {
|
||||||
|
return
|
||||||
|
}
|
||||||
affected, err := DeleteInvitation(invitation)
|
affected, err := DeleteInvitation(invitation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -738,6 +810,9 @@ func initDefinedSubscription(subscription *Subscription) {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if existed != nil {
|
if existed != nil {
|
||||||
|
if initDataNewOnly {
|
||||||
|
return
|
||||||
|
}
|
||||||
affected, err := DeleteSubscription(subscription)
|
affected, err := DeleteSubscription(subscription)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -759,6 +834,9 @@ func initDefinedTransaction(transaction *Transaction) {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if existed != nil {
|
if existed != nil {
|
||||||
|
if initDataNewOnly {
|
||||||
|
return
|
||||||
|
}
|
||||||
affected, err := DeleteTransaction(transaction)
|
affected, err := DeleteTransaction(transaction)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -32,6 +32,7 @@ type Ldap struct {
|
|||||||
BaseDn string `xorm:"varchar(100)" json:"baseDn"`
|
BaseDn string `xorm:"varchar(100)" json:"baseDn"`
|
||||||
Filter string `xorm:"varchar(200)" json:"filter"`
|
Filter string `xorm:"varchar(200)" json:"filter"`
|
||||||
FilterFields []string `xorm:"varchar(100)" json:"filterFields"`
|
FilterFields []string `xorm:"varchar(100)" json:"filterFields"`
|
||||||
|
DefaultGroup string `xorm:"varchar(100)" json:"defaultGroup"`
|
||||||
|
|
||||||
AutoSync int `json:"autoSync"`
|
AutoSync int `json:"autoSync"`
|
||||||
LastSync string `xorm:"varchar(100)" json:"lastSync"`
|
LastSync string `xorm:"varchar(100)" json:"lastSync"`
|
||||||
@ -148,7 +149,7 @@ func UpdateLdap(ldap *Ldap) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
affected, err := ormer.Engine.ID(ldap.Id).Cols("owner", "server_name", "host",
|
affected, err := ormer.Engine.ID(ldap.Id).Cols("owner", "server_name", "host",
|
||||||
"port", "enable_ssl", "username", "password", "base_dn", "filter", "filter_fields", "auto_sync").Update(ldap)
|
"port", "enable_ssl", "username", "password", "base_dn", "filter", "filter_fields", "auto_sync", "default_group").Update(ldap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
@ -339,6 +339,10 @@ func SyncLdapUsers(owner string, syncUsers []LdapUser, ldapId string) (existUser
|
|||||||
Ldap: syncUser.Uuid,
|
Ldap: syncUser.Uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ldap.DefaultGroup != "" {
|
||||||
|
newUser.Groups = []string{ldap.DefaultGroup}
|
||||||
|
}
|
||||||
|
|
||||||
affected, err := AddUser(newUser)
|
affected, err := AddUser(newUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
@ -44,6 +44,18 @@ type OidcDiscovery struct {
|
|||||||
EndSessionEndpoint string `json:"end_session_endpoint"`
|
EndSessionEndpoint string `json:"end_session_endpoint"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WebFinger struct {
|
||||||
|
Subject string `json:"subject"`
|
||||||
|
Links []WebFingerLink `json:"links"`
|
||||||
|
Aliases *[]string `json:"aliases,omitempty"`
|
||||||
|
Properties *map[string]string `json:"properties,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebFingerLink struct {
|
||||||
|
Rel string `json:"rel"`
|
||||||
|
Href string `json:"href"`
|
||||||
|
}
|
||||||
|
|
||||||
func isIpAddress(host string) bool {
|
func isIpAddress(host string) bool {
|
||||||
// Attempt to split the host and port, ignoring the error
|
// Attempt to split the host and port, ignoring the error
|
||||||
hostWithoutPort, _, err := net.SplitHostPort(host)
|
hostWithoutPort, _, err := net.SplitHostPort(host)
|
||||||
@ -112,7 +124,7 @@ func GetOidcDiscovery(host string) OidcDiscovery {
|
|||||||
ResponseModesSupported: []string{"query", "fragment", "login", "code", "link"},
|
ResponseModesSupported: []string{"query", "fragment", "login", "code", "link"},
|
||||||
GrantTypesSupported: []string{"password", "authorization_code"},
|
GrantTypesSupported: []string{"password", "authorization_code"},
|
||||||
SubjectTypesSupported: []string{"public"},
|
SubjectTypesSupported: []string{"public"},
|
||||||
IdTokenSigningAlgValuesSupported: []string{"RS256"},
|
IdTokenSigningAlgValuesSupported: []string{"RS256", "RS512", "ES256", "ES384", "ES512"},
|
||||||
ScopesSupported: []string{"openid", "email", "profile", "address", "phone", "offline_access"},
|
ScopesSupported: []string{"openid", "email", "profile", "address", "phone", "offline_access"},
|
||||||
ClaimsSupported: []string{"iss", "ver", "sub", "aud", "iat", "exp", "id", "type", "displayName", "avatar", "permanentAvatar", "email", "phone", "location", "affiliation", "title", "homepage", "bio", "tag", "region", "language", "score", "ranking", "isOnline", "isAdmin", "isForbidden", "signupApplication", "ldap"},
|
ClaimsSupported: []string{"iss", "ver", "sub", "aud", "iat", "exp", "id", "type", "displayName", "avatar", "permanentAvatar", "email", "phone", "location", "affiliation", "title", "homepage", "bio", "tag", "region", "language", "score", "ranking", "isOnline", "isAdmin", "isForbidden", "signupApplication", "ldap"},
|
||||||
RequestParameterSupported: true,
|
RequestParameterSupported: true,
|
||||||
@ -160,3 +172,43 @@ func GetJsonWebKeySet() (jose.JSONWebKeySet, error) {
|
|||||||
|
|
||||||
return jwks, nil
|
return jwks, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetWebFinger(resource string, rels []string, host string) (WebFinger, error) {
|
||||||
|
wf := WebFinger{}
|
||||||
|
|
||||||
|
resourceSplit := strings.Split(resource, ":")
|
||||||
|
|
||||||
|
if len(resourceSplit) != 2 {
|
||||||
|
return wf, fmt.Errorf("invalid resource")
|
||||||
|
}
|
||||||
|
|
||||||
|
resourceType := resourceSplit[0]
|
||||||
|
resourceValue := resourceSplit[1]
|
||||||
|
|
||||||
|
oidcDiscovery := GetOidcDiscovery(host)
|
||||||
|
|
||||||
|
switch resourceType {
|
||||||
|
case "acct":
|
||||||
|
user, err := GetUserByEmailOnly(resourceValue)
|
||||||
|
if err != nil {
|
||||||
|
return wf, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if user == nil {
|
||||||
|
return wf, fmt.Errorf("user not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
wf.Subject = resource
|
||||||
|
|
||||||
|
for _, rel := range rels {
|
||||||
|
if rel == "http://openid.net/specs/connect/1.0/issuer" {
|
||||||
|
wf.Links = append(wf.Links, WebFingerLink{
|
||||||
|
Rel: "http://openid.net/specs/connect/1.0/issuer",
|
||||||
|
Href: oidcDiscovery.Issuer,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return wf, nil
|
||||||
|
}
|
||||||
|
@ -56,10 +56,13 @@ type Organization struct {
|
|||||||
WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"`
|
WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"`
|
||||||
Logo string `xorm:"varchar(200)" json:"logo"`
|
Logo string `xorm:"varchar(200)" json:"logo"`
|
||||||
LogoDark string `xorm:"varchar(200)" json:"logoDark"`
|
LogoDark string `xorm:"varchar(200)" json:"logoDark"`
|
||||||
Favicon string `xorm:"varchar(100)" json:"favicon"`
|
Favicon string `xorm:"varchar(200)" json:"favicon"`
|
||||||
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
|
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
|
||||||
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
||||||
PasswordOptions []string `xorm:"varchar(100)" json:"passwordOptions"`
|
PasswordOptions []string `xorm:"varchar(100)" json:"passwordOptions"`
|
||||||
|
PasswordObfuscatorType string `xorm:"varchar(100)" json:"passwordObfuscatorType"`
|
||||||
|
PasswordObfuscatorKey string `xorm:"varchar(100)" json:"passwordObfuscatorKey"`
|
||||||
|
PasswordExpireDays int `json:"passwordExpireDays"`
|
||||||
CountryCodes []string `xorm:"varchar(200)" json:"countryCodes"`
|
CountryCodes []string `xorm:"varchar(200)" json:"countryCodes"`
|
||||||
DefaultAvatar string `xorm:"varchar(200)" json:"defaultAvatar"`
|
DefaultAvatar string `xorm:"varchar(200)" json:"defaultAvatar"`
|
||||||
DefaultApplication string `xorm:"varchar(100)" json:"defaultApplication"`
|
DefaultApplication string `xorm:"varchar(100)" json:"defaultApplication"`
|
||||||
@ -69,19 +72,21 @@ type Organization struct {
|
|||||||
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
|
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
|
||||||
DefaultPassword string `xorm:"varchar(100)" json:"defaultPassword"`
|
DefaultPassword string `xorm:"varchar(100)" json:"defaultPassword"`
|
||||||
MasterVerificationCode string `xorm:"varchar(100)" json:"masterVerificationCode"`
|
MasterVerificationCode string `xorm:"varchar(100)" json:"masterVerificationCode"`
|
||||||
|
IpWhitelist string `xorm:"varchar(200)" json:"ipWhitelist"`
|
||||||
InitScore int `json:"initScore"`
|
InitScore int `json:"initScore"`
|
||||||
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
||||||
IsProfilePublic bool `json:"isProfilePublic"`
|
IsProfilePublic bool `json:"isProfilePublic"`
|
||||||
UseEmailAsUsername bool `json:"useEmailAsUsername"`
|
UseEmailAsUsername bool `json:"useEmailAsUsername"`
|
||||||
EnableTour bool `json:"enableTour"`
|
EnableTour bool `json:"enableTour"`
|
||||||
|
IpRestriction string `json:"ipRestriction"`
|
||||||
|
|
||||||
MfaItems []*MfaItem `xorm:"varchar(300)" json:"mfaItems"`
|
MfaItems []*MfaItem `xorm:"varchar(300)" json:"mfaItems"`
|
||||||
AccountItems []*AccountItem `xorm:"varchar(5000)" json:"accountItems"`
|
AccountItems []*AccountItem `xorm:"varchar(5000)" json:"accountItems"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetOrganizationCount(owner, field, value string) (int64, error) {
|
func GetOrganizationCount(owner, name, field, value string) (int64, error) {
|
||||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||||
return session.Count(&Organization{})
|
return session.Count(&Organization{Name: name})
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetOrganizations(owner string, name ...string) ([]*Organization, error) {
|
func GetOrganizations(owner string, name ...string) ([]*Organization, error) {
|
||||||
@ -319,6 +324,7 @@ func GetDefaultApplication(id string) (*Application, error) {
|
|||||||
if defaultApplication == nil {
|
if defaultApplication == nil {
|
||||||
return nil, fmt.Errorf("The default application: %s does not exist", organization.DefaultApplication)
|
return nil, fmt.Errorf("The default application: %s does not exist", organization.DefaultApplication)
|
||||||
} else {
|
} else {
|
||||||
|
defaultApplication.Organization = organization.Name
|
||||||
return defaultApplication, nil
|
return defaultApplication, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -364,7 +364,7 @@ func GetAllActions(userId string) ([]string, error) {
|
|||||||
|
|
||||||
res := []string{}
|
res := []string{}
|
||||||
for _, enforcer := range enforcers {
|
for _, enforcer := range enforcers {
|
||||||
items := enforcer.GetAllObjects()
|
items := enforcer.GetAllActions()
|
||||||
res = append(res, items...)
|
res = append(res, items...)
|
||||||
}
|
}
|
||||||
return res, nil
|
return res, nil
|
||||||
|
@ -50,7 +50,7 @@ func maskPassword(recordString string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewRecord(ctx *context.Context) (*casvisorsdk.Record, error) {
|
func NewRecord(ctx *context.Context) (*casvisorsdk.Record, error) {
|
||||||
ip := strings.Replace(util.GetIPFromRequest(ctx.Request), ": ", "", -1)
|
clientIp := strings.Replace(util.GetClientIpFromRequest(ctx.Request), ": ", "", -1)
|
||||||
action := strings.Replace(ctx.Request.URL.Path, "/api/", "", -1)
|
action := strings.Replace(ctx.Request.URL.Path, "/api/", "", -1)
|
||||||
requestUri := util.FilterQuery(ctx.Request.RequestURI, []string{"accessToken"})
|
requestUri := util.FilterQuery(ctx.Request.RequestURI, []string{"accessToken"})
|
||||||
if len(requestUri) > 1000 {
|
if len(requestUri) > 1000 {
|
||||||
@ -83,7 +83,7 @@ func NewRecord(ctx *context.Context) (*casvisorsdk.Record, error) {
|
|||||||
record := casvisorsdk.Record{
|
record := casvisorsdk.Record{
|
||||||
Name: util.GenerateId(),
|
Name: util.GenerateId(),
|
||||||
CreatedTime: util.GetCurrentTime(),
|
CreatedTime: util.GetCurrentTime(),
|
||||||
ClientIp: ip,
|
ClientIp: clientIp,
|
||||||
User: "",
|
User: "",
|
||||||
Method: ctx.Request.Method,
|
Method: ctx.Request.Method,
|
||||||
RequestUri: requestUri,
|
RequestUri: requestUri,
|
||||||
|
@ -36,7 +36,7 @@ type Resource struct {
|
|||||||
FileType string `xorm:"varchar(100)" json:"fileType"`
|
FileType string `xorm:"varchar(100)" json:"fileType"`
|
||||||
FileFormat string `xorm:"varchar(100)" json:"fileFormat"`
|
FileFormat string `xorm:"varchar(100)" json:"fileFormat"`
|
||||||
FileSize int `json:"fileSize"`
|
FileSize int `json:"fileSize"`
|
||||||
Url string `xorm:"varchar(255)" json:"url"`
|
Url string `xorm:"varchar(500)" json:"url"`
|
||||||
Description string `xorm:"varchar(255)" json:"description"`
|
Description string `xorm:"varchar(255)" json:"description"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/beevik/etree"
|
"github.com/beevik/etree"
|
||||||
@ -65,7 +66,11 @@ func NewSamlResponse(application *Application, user *User, host string, certific
|
|||||||
assertion.CreateAttr("IssueInstant", now)
|
assertion.CreateAttr("IssueInstant", now)
|
||||||
assertion.CreateElement("saml:Issuer").SetText(host)
|
assertion.CreateElement("saml:Issuer").SetText(host)
|
||||||
subject := assertion.CreateElement("saml:Subject")
|
subject := assertion.CreateElement("saml:Subject")
|
||||||
subject.CreateElement("saml:NameID").SetText(user.Name)
|
nameIDValue := user.Name
|
||||||
|
if application.UseEmailAsSamlNameId {
|
||||||
|
nameIDValue = user.Email
|
||||||
|
}
|
||||||
|
subject.CreateElement("saml:NameID").SetText(nameIDValue)
|
||||||
subjectConfirmation := subject.CreateElement("saml:SubjectConfirmation")
|
subjectConfirmation := subject.CreateElement("saml:SubjectConfirmation")
|
||||||
subjectConfirmation.CreateAttr("Method", "urn:oasis:names:tc:SAML:2.0:cm:bearer")
|
subjectConfirmation.CreateAttr("Method", "urn:oasis:names:tc:SAML:2.0:cm:bearer")
|
||||||
subjectConfirmationData := subjectConfirmation.CreateElement("saml:SubjectConfirmationData")
|
subjectConfirmationData := subjectConfirmation.CreateElement("saml:SubjectConfirmationData")
|
||||||
@ -184,17 +189,17 @@ type NameIDFormat struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SingleSignOnService struct {
|
type SingleSignOnService struct {
|
||||||
XMLName xml.Name
|
// XMLName xml.Name
|
||||||
Binding string `xml:"Binding,attr"`
|
Binding string `xml:"Binding,attr"`
|
||||||
Location string `xml:"Location,attr"`
|
Location string `xml:"Location,attr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Attribute struct {
|
type Attribute struct {
|
||||||
// XMLName xml.Name
|
// XMLName xml.Name
|
||||||
|
Xmlns string `xml:"xmlns,attr"`
|
||||||
Name string `xml:"Name,attr"`
|
Name string `xml:"Name,attr"`
|
||||||
NameFormat string `xml:"NameFormat,attr"`
|
NameFormat string `xml:"NameFormat,attr"`
|
||||||
FriendlyName string `xml:"FriendlyName,attr"`
|
FriendlyName string `xml:"FriendlyName,attr"`
|
||||||
Xmlns string `xml:"xmlns,attr"`
|
|
||||||
Values []string `xml:"AttributeValue"`
|
Values []string `xml:"AttributeValue"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,10 +223,13 @@ func GetSamlMeta(application *Application, host string, enablePostBinding bool)
|
|||||||
originFrontend, originBackend := getOriginFromHost(host)
|
originFrontend, originBackend := getOriginFromHost(host)
|
||||||
|
|
||||||
idpLocation := ""
|
idpLocation := ""
|
||||||
|
idpBinding := ""
|
||||||
if enablePostBinding {
|
if enablePostBinding {
|
||||||
idpLocation = fmt.Sprintf("%s/api/saml/redirect/%s/%s", originBackend, application.Owner, application.Name)
|
idpLocation = fmt.Sprintf("%s/api/saml/redirect/%s/%s", originBackend, application.Owner, application.Name)
|
||||||
|
idpBinding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
||||||
} else {
|
} else {
|
||||||
idpLocation = fmt.Sprintf("%s/login/saml/authorize/%s/%s", originFrontend, application.Owner, application.Name)
|
idpLocation = fmt.Sprintf("%s/login/saml/authorize/%s/%s", originFrontend, application.Owner, application.Name)
|
||||||
|
idpBinding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
||||||
}
|
}
|
||||||
|
|
||||||
d := IdpEntityDescriptor{
|
d := IdpEntityDescriptor{
|
||||||
@ -254,7 +262,7 @@ func GetSamlMeta(application *Application, host string, enablePostBinding bool)
|
|||||||
{Xmlns: "urn:oasis:names:tc:SAML:2.0:assertion", Name: "Name", NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:basic", FriendlyName: "Name"},
|
{Xmlns: "urn:oasis:names:tc:SAML:2.0:assertion", Name: "Name", NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:basic", FriendlyName: "Name"},
|
||||||
},
|
},
|
||||||
SingleSignOnService: SingleSignOnService{
|
SingleSignOnService: SingleSignOnService{
|
||||||
Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
|
Binding: idpBinding,
|
||||||
Location: idpLocation,
|
Location: idpLocation,
|
||||||
},
|
},
|
||||||
ProtocolSupportEnumeration: "urn:oasis:names:tc:SAML:2.0:protocol",
|
ProtocolSupportEnumeration: "urn:oasis:names:tc:SAML:2.0:protocol",
|
||||||
@ -269,29 +277,38 @@ func GetSamlMeta(application *Application, host string, enablePostBinding bool)
|
|||||||
func GetSamlResponse(application *Application, user *User, samlRequest string, host string) (string, string, string, error) {
|
func GetSamlResponse(application *Application, user *User, samlRequest string, host string) (string, string, string, error) {
|
||||||
// request type
|
// request type
|
||||||
method := "GET"
|
method := "GET"
|
||||||
|
samlRequest = strings.ReplaceAll(samlRequest, " ", "+")
|
||||||
// base64 decode
|
// base64 decode
|
||||||
defated, err := base64.StdEncoding.DecodeString(samlRequest)
|
defated, err := base64.StdEncoding.DecodeString(samlRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", fmt.Errorf("err: Failed to decode SAML request, %s", err.Error())
|
return "", "", "", fmt.Errorf("err: Failed to decode SAML request, %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// decompress
|
var requestByte []byte
|
||||||
var buffer bytes.Buffer
|
|
||||||
rdr := flate.NewReader(bytes.NewReader(defated))
|
|
||||||
|
|
||||||
for {
|
if strings.Contains(string(defated), "xmlns:") {
|
||||||
_, err = io.CopyN(&buffer, rdr, 1024)
|
requestByte = defated
|
||||||
if err != nil {
|
} else {
|
||||||
if err == io.EOF {
|
// decompress
|
||||||
break
|
var buffer bytes.Buffer
|
||||||
|
rdr := flate.NewReader(bytes.NewReader(defated))
|
||||||
|
|
||||||
|
for {
|
||||||
|
|
||||||
|
_, err = io.CopyN(&buffer, rdr, 1024)
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return "", "", "", err
|
||||||
}
|
}
|
||||||
return "", "", "", err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
requestByte = buffer.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
var authnRequest saml.AuthNRequest
|
var authnRequest saml.AuthNRequest
|
||||||
err = xml.Unmarshal(buffer.Bytes(), &authnRequest)
|
err = xml.Unmarshal(requestByte, &authnRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", fmt.Errorf("err: Failed to unmarshal AuthnRequest, please check the SAML request, %s", err.Error())
|
return "", "", "", fmt.Errorf("err: Failed to unmarshal AuthnRequest, please check the SAML request, %s", err.Error())
|
||||||
}
|
}
|
||||||
@ -386,7 +403,7 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewSamlResponse11 return a saml1.1 response(not 2.0)
|
// NewSamlResponse11 return a saml1.1 response(not 2.0)
|
||||||
func NewSamlResponse11(user *User, requestID string, host string) (*etree.Element, error) {
|
func NewSamlResponse11(application *Application, user *User, requestID string, host string) (*etree.Element, error) {
|
||||||
samlResponse := &etree.Element{
|
samlResponse := &etree.Element{
|
||||||
Space: "samlp",
|
Space: "samlp",
|
||||||
Tag: "Response",
|
Tag: "Response",
|
||||||
@ -430,7 +447,11 @@ func NewSamlResponse11(user *User, requestID string, host string) (*etree.Elemen
|
|||||||
// nameIdentifier inside subject
|
// nameIdentifier inside subject
|
||||||
nameIdentifier := subject.CreateElement("saml:NameIdentifier")
|
nameIdentifier := subject.CreateElement("saml:NameIdentifier")
|
||||||
// nameIdentifier.CreateAttr("Format", "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress")
|
// nameIdentifier.CreateAttr("Format", "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress")
|
||||||
nameIdentifier.SetText(user.Name)
|
if application.UseEmailAsSamlNameId {
|
||||||
|
nameIdentifier.SetText(user.Email)
|
||||||
|
} else {
|
||||||
|
nameIdentifier.SetText(user.Name)
|
||||||
|
}
|
||||||
|
|
||||||
// subjectConfirmation inside subject
|
// subjectConfirmation inside subject
|
||||||
subjectConfirmation := subject.CreateElement("saml:SubjectConfirmation")
|
subjectConfirmation := subject.CreateElement("saml:SubjectConfirmation")
|
||||||
@ -439,7 +460,11 @@ func NewSamlResponse11(user *User, requestID string, host string) (*etree.Elemen
|
|||||||
attributeStatement := assertion.CreateElement("saml:AttributeStatement")
|
attributeStatement := assertion.CreateElement("saml:AttributeStatement")
|
||||||
subjectInAttribute := attributeStatement.CreateElement("saml:Subject")
|
subjectInAttribute := attributeStatement.CreateElement("saml:Subject")
|
||||||
nameIdentifierInAttribute := subjectInAttribute.CreateElement("saml:NameIdentifier")
|
nameIdentifierInAttribute := subjectInAttribute.CreateElement("saml:NameIdentifier")
|
||||||
nameIdentifierInAttribute.SetText(user.Name)
|
if application.UseEmailAsSamlNameId {
|
||||||
|
nameIdentifierInAttribute.SetText(user.Email)
|
||||||
|
} else {
|
||||||
|
nameIdentifierInAttribute.SetText(user.Name)
|
||||||
|
}
|
||||||
|
|
||||||
subjectConfirmationInAttribute := subjectInAttribute.CreateElement("saml:SubjectConfirmation")
|
subjectConfirmationInAttribute := subjectInAttribute.CreateElement("saml:SubjectConfirmation")
|
||||||
subjectConfirmationInAttribute.CreateElement("saml:ConfirmationMethod").SetText("urn:oasis:names:tc:SAML:1.0:cm:artifact")
|
subjectConfirmationInAttribute.CreateElement("saml:ConfirmationMethod").SetText("urn:oasis:names:tc:SAML:1.0:cm:artifact")
|
||||||
|
@ -30,6 +30,13 @@ import (
|
|||||||
|
|
||||||
var isCloudIntranet bool
|
var isCloudIntranet bool
|
||||||
|
|
||||||
|
const (
|
||||||
|
ProviderTypeGoogleCloudStorage = "Google Cloud Storage"
|
||||||
|
ProviderTypeTencentCloudCOS = "Tencent Cloud COS"
|
||||||
|
ProviderTypeAzureBlob = "Azure Blob"
|
||||||
|
ProviderTypeLocalFileSystem = "Local File System"
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
isCloudIntranet = conf.GetConfigBool("isCloudIntranet")
|
isCloudIntranet = conf.GetConfigBool("isCloudIntranet")
|
||||||
}
|
}
|
||||||
@ -80,27 +87,28 @@ func GetUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool
|
|||||||
objectKey := util.UrlJoin(util.GetUrlPath(provider.Domain), escapedPath)
|
objectKey := util.UrlJoin(util.GetUrlPath(provider.Domain), escapedPath)
|
||||||
|
|
||||||
host := ""
|
host := ""
|
||||||
if provider.Type != "Local File System" {
|
if provider.Type != ProviderTypeLocalFileSystem {
|
||||||
// provider.Domain = "https://cdn.casbin.com/casdoor/"
|
// provider.Domain = "https://cdn.casbin.com/casdoor/"
|
||||||
host = util.GetUrlHost(provider.Domain)
|
host = util.GetUrlHost(provider.Domain)
|
||||||
} else {
|
} else {
|
||||||
// provider.Domain = "http://localhost:8000" or "https://door.casdoor.com"
|
// provider.Domain = "http://localhost:8000" or "https://door.casdoor.com"
|
||||||
host = util.UrlJoin(provider.Domain, "/files")
|
host = util.UrlJoin(provider.Domain, "/files")
|
||||||
}
|
}
|
||||||
if provider.Type == "Azure Blob" {
|
if provider.Type == ProviderTypeAzureBlob || provider.Type == ProviderTypeGoogleCloudStorage {
|
||||||
host = util.UrlJoin(host, provider.Bucket)
|
host = util.UrlJoin(host, provider.Bucket)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileUrl := ""
|
fileUrl := ""
|
||||||
if host != "" {
|
if host != "" {
|
||||||
fileUrl = util.UrlJoin(host, escapePath(objectKey))
|
// fileUrl = util.UrlJoin(host, escapePath(objectKey))
|
||||||
|
fileUrl = util.UrlJoin(host, objectKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
if fileUrl != "" && hasTimestamp {
|
// if fileUrl != "" && hasTimestamp {
|
||||||
fileUrl = fmt.Sprintf("%s?t=%s", fileUrl, util.GetCurrentUnixTime())
|
// fileUrl = fmt.Sprintf("%s?t=%s", fileUrl, util.GetCurrentUnixTime())
|
||||||
}
|
// }
|
||||||
|
|
||||||
if provider.Type == "Tencent Cloud COS" {
|
if provider.Type == ProviderTypeTencentCloudCOS {
|
||||||
objectKey = escapePath(objectKey)
|
objectKey = escapePath(objectKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,7 +117,18 @@ func GetUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool
|
|||||||
|
|
||||||
func getStorageProvider(provider *Provider, lang string) (oss.StorageInterface, error) {
|
func getStorageProvider(provider *Provider, lang string) (oss.StorageInterface, error) {
|
||||||
endpoint := getProviderEndpoint(provider)
|
endpoint := getProviderEndpoint(provider)
|
||||||
storageProvider, err := storage.GetStorageProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.RegionId, provider.Bucket, endpoint)
|
certificate := ""
|
||||||
|
if provider.Category == "Storage" && provider.Type == "Casdoor" {
|
||||||
|
cert, err := GetCert(util.GetId(provider.Owner, provider.Cert))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if cert == nil {
|
||||||
|
return nil, fmt.Errorf("no cert for %s", provider.Cert)
|
||||||
|
}
|
||||||
|
certificate = cert.Certificate
|
||||||
|
}
|
||||||
|
storageProvider, err := storage.GetStorageProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.RegionId, provider.Bucket, endpoint, certificate, provider.Content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -135,17 +154,17 @@ func uploadFile(provider *Provider, fullFilePath string, fileBuffer *bytes.Buffe
|
|||||||
}
|
}
|
||||||
|
|
||||||
fileUrl, objectKey := GetUploadFileUrl(provider, fullFilePath, true)
|
fileUrl, objectKey := GetUploadFileUrl(provider, fullFilePath, true)
|
||||||
|
objectKeyRefined := refineObjectKey(provider, objectKey)
|
||||||
|
|
||||||
objectKeyRefined := objectKey
|
object, err := storageProvider.Put(objectKeyRefined, fileBuffer)
|
||||||
if provider.Type == "Google Cloud Storage" {
|
|
||||||
objectKeyRefined = strings.TrimPrefix(objectKeyRefined, "/")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = storageProvider.Put(objectKeyRefined, fileBuffer)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if provider.Type == "Casdoor" {
|
||||||
|
fileUrl = object.Path
|
||||||
|
}
|
||||||
|
|
||||||
return fileUrl, objectKey, nil
|
return fileUrl, objectKey, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,5 +203,13 @@ func DeleteFile(provider *Provider, objectKey string, lang string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return storageProvider.Delete(objectKey)
|
objectKeyRefined := refineObjectKey(provider, objectKey)
|
||||||
|
return storageProvider.Delete(objectKeyRefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
func refineObjectKey(provider *Provider, objectKey string) string {
|
||||||
|
if provider.Type == ProviderTypeGoogleCloudStorage {
|
||||||
|
return strings.TrimPrefix(objectKey, "/")
|
||||||
|
}
|
||||||
|
return objectKey
|
||||||
}
|
}
|
||||||
|
@ -102,14 +102,6 @@ func GetTokenByAccessToken(accessToken string) (*Token, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !existed {
|
|
||||||
token = Token{AccessToken: accessToken}
|
|
||||||
existed, err = ormer.Engine.Get(&token)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !existed {
|
if !existed {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@ -123,14 +115,6 @@ func GetTokenByRefreshToken(refreshToken string) (*Token, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !existed {
|
|
||||||
token = Token{RefreshToken: refreshToken}
|
|
||||||
existed, err = ormer.Engine.Get(&token)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !existed {
|
if !existed {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@ -140,6 +124,7 @@ func GetTokenByRefreshToken(refreshToken string) (*Token, error) {
|
|||||||
func GetTokenByTokenValue(tokenValue, tokenTypeHint string) (*Token, error) {
|
func GetTokenByTokenValue(tokenValue, tokenTypeHint string) (*Token, error) {
|
||||||
switch tokenTypeHint {
|
switch tokenTypeHint {
|
||||||
case "access_token":
|
case "access_token":
|
||||||
|
case "access-token":
|
||||||
token, err := GetTokenByAccessToken(tokenValue)
|
token, err := GetTokenByAccessToken(tokenValue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -148,6 +133,7 @@ func GetTokenByTokenValue(tokenValue, tokenTypeHint string) (*Token, error) {
|
|||||||
return token, nil
|
return token, nil
|
||||||
}
|
}
|
||||||
case "refresh_token":
|
case "refresh_token":
|
||||||
|
case "refresh-token":
|
||||||
token, err := GetTokenByRefreshToken(tokenValue)
|
token, err := GetTokenByRefreshToken(tokenValue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -277,12 +277,11 @@ func GetValidationBySaml(samlRequest string, host string) (string, string, error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if application == nil {
|
if application == nil {
|
||||||
return "", "", fmt.Errorf("the application for user %s is not found", userId)
|
return "", "", fmt.Errorf("the application for user %s is not found", userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
samlResponse, err := NewSamlResponse11(user, request.RequestID, host)
|
samlResponse, err := NewSamlResponse11(application, user, request.RequestID, host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ package object
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
@ -128,7 +129,7 @@ type UserWithoutThirdIdp struct {
|
|||||||
LastSigninWrongTime string `xorm:"varchar(100)" json:"lastSigninWrongTime"`
|
LastSigninWrongTime string `xorm:"varchar(100)" json:"lastSigninWrongTime"`
|
||||||
SigninWrongTimes int `json:"signinWrongTimes"`
|
SigninWrongTimes int `json:"signinWrongTimes"`
|
||||||
|
|
||||||
// ManagedAccounts []ManagedAccount `xorm:"managedAccounts blob" json:"managedAccounts"`
|
ManagedAccounts []ManagedAccount `xorm:"managedAccounts blob" json:"managedAccounts"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClaimsShort struct {
|
type ClaimsShort struct {
|
||||||
@ -254,6 +255,8 @@ func getUserWithoutThirdIdp(user *User) *UserWithoutThirdIdp {
|
|||||||
|
|
||||||
LastSigninWrongTime: user.LastSigninWrongTime,
|
LastSigninWrongTime: user.LastSigninWrongTime,
|
||||||
SigninWrongTimes: user.SigninWrongTimes,
|
SigninWrongTimes: user.SigninWrongTimes,
|
||||||
|
|
||||||
|
ManagedAccounts: user.ManagedAccounts,
|
||||||
}
|
}
|
||||||
|
|
||||||
return res
|
return res
|
||||||
@ -365,6 +368,10 @@ func generateJwtToken(application *Application, user *User, nonce string, scope
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if application.IsShared {
|
||||||
|
claims.Audience = []string{application.ClientId + "-org-" + user.Owner}
|
||||||
|
}
|
||||||
|
|
||||||
var token *jwt.Token
|
var token *jwt.Token
|
||||||
var refreshToken *jwt.Token
|
var refreshToken *jwt.Token
|
||||||
|
|
||||||
@ -372,36 +379,52 @@ func generateJwtToken(application *Application, user *User, nonce string, scope
|
|||||||
application.TokenFormat = "JWT"
|
application.TokenFormat = "JWT"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var jwtMethod jwt.SigningMethod
|
||||||
|
|
||||||
|
if application.TokenSigningMethod == "RS256" {
|
||||||
|
jwtMethod = jwt.SigningMethodRS256
|
||||||
|
} else if application.TokenSigningMethod == "RS512" {
|
||||||
|
jwtMethod = jwt.SigningMethodRS512
|
||||||
|
} else if application.TokenSigningMethod == "ES256" {
|
||||||
|
jwtMethod = jwt.SigningMethodES256
|
||||||
|
} else if application.TokenSigningMethod == "ES512" {
|
||||||
|
jwtMethod = jwt.SigningMethodES512
|
||||||
|
} else if application.TokenSigningMethod == "ES384" {
|
||||||
|
jwtMethod = jwt.SigningMethodES384
|
||||||
|
} else {
|
||||||
|
jwtMethod = jwt.SigningMethodRS256
|
||||||
|
}
|
||||||
|
|
||||||
// the JWT token length in "JWT-Empty" mode will be very short, as User object only has two properties: owner and name
|
// the JWT token length in "JWT-Empty" mode will be very short, as User object only has two properties: owner and name
|
||||||
if application.TokenFormat == "JWT" {
|
if application.TokenFormat == "JWT" {
|
||||||
claimsWithoutThirdIdp := getClaimsWithoutThirdIdp(claims)
|
claimsWithoutThirdIdp := getClaimsWithoutThirdIdp(claims)
|
||||||
|
|
||||||
token = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsWithoutThirdIdp)
|
token = jwt.NewWithClaims(jwtMethod, claimsWithoutThirdIdp)
|
||||||
claimsWithoutThirdIdp.ExpiresAt = jwt.NewNumericDate(refreshExpireTime)
|
claimsWithoutThirdIdp.ExpiresAt = jwt.NewNumericDate(refreshExpireTime)
|
||||||
claimsWithoutThirdIdp.TokenType = "refresh-token"
|
claimsWithoutThirdIdp.TokenType = "refresh-token"
|
||||||
refreshToken = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsWithoutThirdIdp)
|
refreshToken = jwt.NewWithClaims(jwtMethod, claimsWithoutThirdIdp)
|
||||||
} else if application.TokenFormat == "JWT-Empty" {
|
} else if application.TokenFormat == "JWT-Empty" {
|
||||||
claimsShort := getShortClaims(claims)
|
claimsShort := getShortClaims(claims)
|
||||||
|
|
||||||
token = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsShort)
|
token = jwt.NewWithClaims(jwtMethod, claimsShort)
|
||||||
claimsShort.ExpiresAt = jwt.NewNumericDate(refreshExpireTime)
|
claimsShort.ExpiresAt = jwt.NewNumericDate(refreshExpireTime)
|
||||||
claimsShort.TokenType = "refresh-token"
|
claimsShort.TokenType = "refresh-token"
|
||||||
refreshToken = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsShort)
|
refreshToken = jwt.NewWithClaims(jwtMethod, claimsShort)
|
||||||
} else if application.TokenFormat == "JWT-Custom" {
|
} else if application.TokenFormat == "JWT-Custom" {
|
||||||
claimsCustom := getClaimsCustom(claims, application.TokenFields)
|
claimsCustom := getClaimsCustom(claims, application.TokenFields)
|
||||||
|
|
||||||
token = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsCustom)
|
token = jwt.NewWithClaims(jwtMethod, claimsCustom)
|
||||||
refreshClaims := getClaimsCustom(claims, application.TokenFields)
|
refreshClaims := getClaimsCustom(claims, application.TokenFields)
|
||||||
refreshClaims["exp"] = jwt.NewNumericDate(refreshExpireTime)
|
refreshClaims["exp"] = jwt.NewNumericDate(refreshExpireTime)
|
||||||
refreshClaims["TokenType"] = "refresh-token"
|
refreshClaims["TokenType"] = "refresh-token"
|
||||||
refreshToken = jwt.NewWithClaims(jwt.SigningMethodRS256, refreshClaims)
|
refreshToken = jwt.NewWithClaims(jwtMethod, refreshClaims)
|
||||||
} else if application.TokenFormat == "JWT-Standard" {
|
} else if application.TokenFormat == "JWT-Standard" {
|
||||||
claimsStandard := getStandardClaims(claims)
|
claimsStandard := getStandardClaims(claims)
|
||||||
|
|
||||||
token = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsStandard)
|
token = jwt.NewWithClaims(jwtMethod, claimsStandard)
|
||||||
claimsStandard.ExpiresAt = jwt.NewNumericDate(refreshExpireTime)
|
claimsStandard.ExpiresAt = jwt.NewNumericDate(refreshExpireTime)
|
||||||
claimsStandard.TokenType = "refresh-token"
|
claimsStandard.TokenType = "refresh-token"
|
||||||
refreshToken = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsStandard)
|
refreshToken = jwt.NewWithClaims(jwtMethod, claimsStandard)
|
||||||
} else {
|
} else {
|
||||||
return "", "", "", fmt.Errorf("unknown application TokenFormat: %s", application.TokenFormat)
|
return "", "", "", fmt.Errorf("unknown application TokenFormat: %s", application.TokenFormat)
|
||||||
}
|
}
|
||||||
@ -419,34 +442,57 @@ func generateJwtToken(application *Application, user *User, nonce string, scope
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RSA private key
|
var (
|
||||||
key, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(cert.PrivateKey))
|
tokenString string
|
||||||
|
refreshTokenString string
|
||||||
|
key interface{}
|
||||||
|
)
|
||||||
|
|
||||||
|
if strings.Contains(application.TokenSigningMethod, "RS") || application.TokenSigningMethod == "" {
|
||||||
|
// RSA private key
|
||||||
|
key, err = jwt.ParseRSAPrivateKeyFromPEM([]byte(cert.PrivateKey))
|
||||||
|
} else if strings.Contains(application.TokenSigningMethod, "ES") {
|
||||||
|
// ES private key
|
||||||
|
key, err = jwt.ParseECPrivateKeyFromPEM([]byte(cert.PrivateKey))
|
||||||
|
} else if strings.Contains(application.TokenSigningMethod, "Ed") {
|
||||||
|
// Ed private key
|
||||||
|
key, err = jwt.ParseEdPrivateKeyFromPEM([]byte(cert.PrivateKey))
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", err
|
return "", "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
token.Header["kid"] = cert.Name
|
token.Header["kid"] = cert.Name
|
||||||
tokenString, err := token.SignedString(key)
|
tokenString, err = token.SignedString(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", err
|
return "", "", "", err
|
||||||
}
|
}
|
||||||
refreshTokenString, err := refreshToken.SignedString(key)
|
refreshTokenString, err = refreshToken.SignedString(key)
|
||||||
|
|
||||||
return tokenString, refreshTokenString, name, err
|
return tokenString, refreshTokenString, name, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseJwtToken(token string, cert *Cert) (*Claims, error) {
|
func ParseJwtToken(token string, cert *Cert) (*Claims, error) {
|
||||||
t, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
|
t, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
|
||||||
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
|
var (
|
||||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
certificate interface{}
|
||||||
}
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
if cert.Certificate == "" {
|
if cert.Certificate == "" {
|
||||||
return nil, fmt.Errorf("the certificate field should not be empty for the cert: %v", cert)
|
return nil, fmt.Errorf("the certificate field should not be empty for the cert: %v", cert)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RSA certificate
|
if _, ok := token.Method.(*jwt.SigningMethodRSA); ok {
|
||||||
certificate, err := jwt.ParseRSAPublicKeyFromPEM([]byte(cert.Certificate))
|
// RSA certificate
|
||||||
|
certificate, err = jwt.ParseRSAPublicKeyFromPEM([]byte(cert.Certificate))
|
||||||
|
} else if _, ok := token.Method.(*jwt.SigningMethodECDSA); ok {
|
||||||
|
// ES certificate
|
||||||
|
certificate, err = jwt.ParseECPublicKeyFromPEM([]byte(cert.Certificate))
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -332,6 +332,9 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if user == nil {
|
||||||
|
return "", fmt.Errorf("The user: %s doesn't exist", util.GetId(application.Organization, token.User))
|
||||||
|
}
|
||||||
|
|
||||||
if user.IsForbidden {
|
if user.IsForbidden {
|
||||||
return &TokenError{
|
return &TokenError{
|
||||||
@ -428,22 +431,26 @@ func GetAuthorizationCodeToken(application *Application, clientSecret string, co
|
|||||||
if token == nil {
|
if token == nil {
|
||||||
return nil, &TokenError{
|
return nil, &TokenError{
|
||||||
Error: InvalidGrant,
|
Error: InvalidGrant,
|
||||||
ErrorDescription: "authorization code is invalid",
|
ErrorDescription: fmt.Sprintf("authorization code: [%s] is invalid", code),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if token.CodeIsUsed {
|
if token.CodeIsUsed {
|
||||||
// anti replay attacks
|
// anti replay attacks
|
||||||
return nil, &TokenError{
|
return nil, &TokenError{
|
||||||
Error: InvalidGrant,
|
Error: InvalidGrant,
|
||||||
ErrorDescription: "authorization code has been used",
|
ErrorDescription: fmt.Sprintf("authorization code has been used for token: [%s]", token.GetId()),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if token.CodeChallenge != "" && pkceChallenge(verifier) != token.CodeChallenge {
|
if token.CodeChallenge != "" {
|
||||||
return nil, &TokenError{
|
challengeAnswer := pkceChallenge(verifier)
|
||||||
Error: InvalidGrant,
|
if challengeAnswer != token.CodeChallenge {
|
||||||
ErrorDescription: "verifier is invalid",
|
return nil, &TokenError{
|
||||||
}, nil
|
Error: InvalidGrant,
|
||||||
|
ErrorDescription: fmt.Sprintf("verifier is invalid, challengeAnswer: [%s], token.CodeChallenge: [%s]", challengeAnswer, token.CodeChallenge),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if application.ClientSecret != clientSecret {
|
if application.ClientSecret != clientSecret {
|
||||||
@ -452,13 +459,13 @@ func GetAuthorizationCodeToken(application *Application, clientSecret string, co
|
|||||||
if token.CodeChallenge == "" {
|
if token.CodeChallenge == "" {
|
||||||
return nil, &TokenError{
|
return nil, &TokenError{
|
||||||
Error: InvalidClient,
|
Error: InvalidClient,
|
||||||
ErrorDescription: "client_secret is invalid",
|
ErrorDescription: fmt.Sprintf("client_secret is invalid for application: [%s], token.CodeChallenge: empty", application.GetId()),
|
||||||
}, nil
|
}, nil
|
||||||
} else {
|
} else {
|
||||||
if clientSecret != "" {
|
if clientSecret != "" {
|
||||||
return nil, &TokenError{
|
return nil, &TokenError{
|
||||||
Error: InvalidClient,
|
Error: InvalidClient,
|
||||||
ErrorDescription: "client_secret is invalid",
|
ErrorDescription: fmt.Sprintf("client_secret is invalid for application: [%s], token.CodeChallenge: [%s]", application.GetId(), token.CodeChallenge),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -467,15 +474,16 @@ func GetAuthorizationCodeToken(application *Application, clientSecret string, co
|
|||||||
if application.Name != token.Application {
|
if application.Name != token.Application {
|
||||||
return nil, &TokenError{
|
return nil, &TokenError{
|
||||||
Error: InvalidGrant,
|
Error: InvalidGrant,
|
||||||
ErrorDescription: "the token is for wrong application (client_id)",
|
ErrorDescription: fmt.Sprintf("the token is for wrong application (client_id), application.Name: [%s], token.Application: [%s]", application.Name, token.Application),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if time.Now().Unix() > token.CodeExpireIn {
|
nowUnix := time.Now().Unix()
|
||||||
|
if nowUnix > token.CodeExpireIn {
|
||||||
// code must be used within 5 minutes
|
// code must be used within 5 minutes
|
||||||
return nil, &TokenError{
|
return nil, &TokenError{
|
||||||
Error: InvalidGrant,
|
Error: InvalidGrant,
|
||||||
ErrorDescription: "authorization code has expired",
|
ErrorDescription: fmt.Sprintf("authorization code has expired, nowUnix: [%s], token.CodeExpireIn: [%s]", time.Unix(nowUnix, 0).Format(time.RFC3339), time.Unix(token.CodeExpireIn, 0).Format(time.RFC3339)),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
return token, nil, nil
|
return token, nil, nil
|
||||||
|
@ -18,16 +18,20 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
"github.com/golang-jwt/jwt/v4"
|
"github.com/golang-jwt/jwt/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ClaimsStandard struct {
|
type ClaimsStandard struct {
|
||||||
*UserShort
|
*UserShort
|
||||||
Gender string `json:"gender,omitempty"`
|
EmailVerified bool `json:"email_verified,omitempty"`
|
||||||
TokenType string `json:"tokenType,omitempty"`
|
PhoneNumber string `json:"phone_number,omitempty"`
|
||||||
Nonce string `json:"nonce,omitempty"`
|
PhoneNumberVerified bool `json:"phone_number_verified,omitempty"`
|
||||||
Scope string `json:"scope,omitempty"`
|
Gender string `json:"gender,omitempty"`
|
||||||
Address OIDCAddress `json:"address,omitempty"`
|
TokenType string `json:"tokenType,omitempty"`
|
||||||
|
Nonce string `json:"nonce,omitempty"`
|
||||||
|
Scope string `json:"scope,omitempty"`
|
||||||
|
Address OIDCAddress `json:"address,omitempty"`
|
||||||
|
|
||||||
jwt.RegisteredClaims
|
jwt.RegisteredClaims
|
||||||
}
|
}
|
||||||
@ -43,12 +47,14 @@ func getStreetAddress(user *User) string {
|
|||||||
func getStandardClaims(claims Claims) ClaimsStandard {
|
func getStandardClaims(claims Claims) ClaimsStandard {
|
||||||
res := ClaimsStandard{
|
res := ClaimsStandard{
|
||||||
UserShort: getShortUser(claims.User),
|
UserShort: getShortUser(claims.User),
|
||||||
|
EmailVerified: claims.User.EmailVerified,
|
||||||
TokenType: claims.TokenType,
|
TokenType: claims.TokenType,
|
||||||
Nonce: claims.Nonce,
|
Nonce: claims.Nonce,
|
||||||
Scope: claims.Scope,
|
Scope: claims.Scope,
|
||||||
RegisteredClaims: claims.RegisteredClaims,
|
RegisteredClaims: claims.RegisteredClaims,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
res.Phone = ""
|
||||||
var scopes []string
|
var scopes []string
|
||||||
|
|
||||||
if strings.Contains(claims.Scope, ",") {
|
if strings.Contains(claims.Scope, ",") {
|
||||||
@ -62,6 +68,15 @@ func getStandardClaims(claims Claims) ClaimsStandard {
|
|||||||
res.Address = OIDCAddress{StreetAddress: getStreetAddress(claims.User)}
|
res.Address = OIDCAddress{StreetAddress: getStreetAddress(claims.User)}
|
||||||
} else if scope == "profile" {
|
} else if scope == "profile" {
|
||||||
res.Gender = claims.User.Gender
|
res.Gender = claims.User.Gender
|
||||||
|
} else if scope == "phone" && claims.User.Phone != "" {
|
||||||
|
res.PhoneNumberVerified = true
|
||||||
|
phoneNumber, ok := util.GetE164Number(claims.User.Phone, claims.User.CountryCode)
|
||||||
|
if !ok {
|
||||||
|
res.PhoneNumberVerified = false
|
||||||
|
} else {
|
||||||
|
res.PhoneNumber = phoneNumber
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,12 +200,14 @@ type User struct {
|
|||||||
Permissions []*Permission `json:"permissions"`
|
Permissions []*Permission `json:"permissions"`
|
||||||
Groups []string `xorm:"groups varchar(1000)" json:"groups"`
|
Groups []string `xorm:"groups varchar(1000)" json:"groups"`
|
||||||
|
|
||||||
LastSigninWrongTime string `xorm:"varchar(100)" json:"lastSigninWrongTime"`
|
LastChangePasswordTime string `xorm:"varchar(100)" json:"lastChangePasswordTime"`
|
||||||
SigninWrongTimes int `json:"signinWrongTimes"`
|
LastSigninWrongTime string `xorm:"varchar(100)" json:"lastSigninWrongTime"`
|
||||||
|
SigninWrongTimes int `json:"signinWrongTimes"`
|
||||||
|
|
||||||
ManagedAccounts []ManagedAccount `xorm:"managedAccounts blob" json:"managedAccounts"`
|
ManagedAccounts []ManagedAccount `xorm:"managedAccounts blob" json:"managedAccounts"`
|
||||||
MfaAccounts []MfaAccount `xorm:"mfaAccounts blob" json:"mfaAccounts"`
|
MfaAccounts []MfaAccount `xorm:"mfaAccounts blob" json:"mfaAccounts"`
|
||||||
NeedUpdatePassword bool `json:"needUpdatePassword"`
|
NeedUpdatePassword bool `json:"needUpdatePassword"`
|
||||||
|
IpWhitelist string `xorm:"varchar(200)" json:"ipWhitelist"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Userinfo struct {
|
type Userinfo struct {
|
||||||
@ -689,14 +691,14 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
|
|||||||
"owner", "display_name", "avatar", "first_name", "last_name",
|
"owner", "display_name", "avatar", "first_name", "last_name",
|
||||||
"location", "address", "country_code", "region", "language", "affiliation", "title", "id_card_type", "id_card", "homepage", "bio", "tag", "language", "gender", "birthday", "education", "score", "karma", "ranking", "signup_application",
|
"location", "address", "country_code", "region", "language", "affiliation", "title", "id_card_type", "id_card", "homepage", "bio", "tag", "language", "gender", "birthday", "education", "score", "karma", "ranking", "signup_application",
|
||||||
"is_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts", "face_ids", "mfaAccounts",
|
"is_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts", "face_ids", "mfaAccounts",
|
||||||
"signin_wrong_times", "last_signin_wrong_time", "groups", "access_key", "access_secret", "mfa_phone_enabled", "mfa_email_enabled",
|
"signin_wrong_times", "last_change_password_time", "last_signin_wrong_time", "groups", "access_key", "access_secret", "mfa_phone_enabled", "mfa_email_enabled",
|
||||||
"github", "google", "qq", "wechat", "facebook", "dingtalk", "weibo", "gitee", "linkedin", "wecom", "lark", "gitlab", "adfs",
|
"github", "google", "qq", "wechat", "facebook", "dingtalk", "weibo", "gitee", "linkedin", "wecom", "lark", "gitlab", "adfs",
|
||||||
"baidu", "alipay", "casdoor", "infoflow", "apple", "azuread", "azureadb2c", "slack", "steam", "bilibili", "okta", "douyin", "line", "amazon",
|
"baidu", "alipay", "casdoor", "infoflow", "apple", "azuread", "azureadb2c", "slack", "steam", "bilibili", "okta", "douyin", "line", "amazon",
|
||||||
"auth0", "battlenet", "bitbucket", "box", "cloudfoundry", "dailymotion", "deezer", "digitalocean", "discord", "dropbox",
|
"auth0", "battlenet", "bitbucket", "box", "cloudfoundry", "dailymotion", "deezer", "digitalocean", "discord", "dropbox",
|
||||||
"eveonline", "fitbit", "gitea", "heroku", "influxcloud", "instagram", "intercom", "kakao", "lastfm", "mailru", "meetup",
|
"eveonline", "fitbit", "gitea", "heroku", "influxcloud", "instagram", "intercom", "kakao", "lastfm", "mailru", "meetup",
|
||||||
"microsoftonline", "naver", "nextcloud", "onedrive", "oura", "patreon", "paypal", "salesforce", "shopify", "soundcloud",
|
"microsoftonline", "naver", "nextcloud", "onedrive", "oura", "patreon", "paypal", "salesforce", "shopify", "soundcloud",
|
||||||
"spotify", "strava", "stripe", "type", "tiktok", "tumblr", "twitch", "twitter", "typetalk", "uber", "vk", "wepay", "xero", "yahoo",
|
"spotify", "strava", "stripe", "type", "tiktok", "tumblr", "twitch", "twitter", "typetalk", "uber", "vk", "wepay", "xero", "yahoo",
|
||||||
"yammer", "yandex", "zoom", "custom", "need_update_password",
|
"yammer", "yandex", "zoom", "custom", "need_update_password", "ip_whitelist",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if isAdmin {
|
if isAdmin {
|
||||||
@ -815,6 +817,10 @@ func AddUser(user *User) (bool, error) {
|
|||||||
user.UpdateUserPassword(organization)
|
user.UpdateUserPassword(organization)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if user.CreatedTime == "" {
|
||||||
|
user.CreatedTime = util.GetCurrentTime()
|
||||||
|
}
|
||||||
|
|
||||||
err = user.UpdateUserHash()
|
err = user.UpdateUserHash()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@ -950,7 +956,17 @@ func DeleteUser(user *User) (bool, error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return deleteUser(user)
|
organization, err := GetOrganizationByUser(user)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if organization != nil && organization.EnableSoftDeletion {
|
||||||
|
user.IsDeleted = true
|
||||||
|
user.DeletedTime = util.GetCurrentTime()
|
||||||
|
return UpdateUser(user.GetId(), user, []string{"is_deleted", "deleted_time"}, false)
|
||||||
|
} else {
|
||||||
|
return deleteUser(user)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetUserInfo(user *User, scope string, aud string, host string) (*Userinfo, error) {
|
func GetUserInfo(user *User, scope string, aud string, host string) (*Userinfo, error) {
|
||||||
@ -1138,7 +1154,7 @@ func (user *User) IsApplicationAdmin(application *Application) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return (user.Owner == application.Organization && user.IsAdmin) || user.IsGlobalAdmin()
|
return (user.Owner == application.Organization && user.IsAdmin) || user.IsGlobalAdmin() || (user.IsAdmin && application.IsShared)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (user *User) IsGlobalAdmin() bool {
|
func (user *User) IsGlobalAdmin() bool {
|
||||||
|
@ -271,113 +271,213 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
|
|
||||||
if oldUser.Owner != newUser.Owner {
|
if oldUser.Owner != newUser.Owner {
|
||||||
item := GetAccountItemByName("Organization", organization)
|
item := GetAccountItemByName("Organization", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Owner = oldUser.Owner
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Name != newUser.Name {
|
if oldUser.Name != newUser.Name {
|
||||||
item := GetAccountItemByName("Name", organization)
|
item := GetAccountItemByName("Name", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Name = oldUser.Name
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Id != newUser.Id {
|
if oldUser.Id != newUser.Id {
|
||||||
item := GetAccountItemByName("ID", organization)
|
item := GetAccountItemByName("ID", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Id = oldUser.Id
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.DisplayName != newUser.DisplayName {
|
if oldUser.DisplayName != newUser.DisplayName {
|
||||||
item := GetAccountItemByName("Display name", organization)
|
item := GetAccountItemByName("Display name", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.DisplayName = oldUser.DisplayName
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Avatar != newUser.Avatar {
|
if oldUser.Avatar != newUser.Avatar {
|
||||||
item := GetAccountItemByName("Avatar", organization)
|
item := GetAccountItemByName("Avatar", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Avatar = oldUser.Avatar
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Type != newUser.Type {
|
if oldUser.Type != newUser.Type {
|
||||||
item := GetAccountItemByName("User type", organization)
|
item := GetAccountItemByName("User type", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Type = oldUser.Type
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// The password is *** when not modified
|
// The password is *** when not modified
|
||||||
if oldUser.Password != newUser.Password && newUser.Password != "***" {
|
if oldUser.Password != newUser.Password && newUser.Password != "***" {
|
||||||
item := GetAccountItemByName("Password", organization)
|
item := GetAccountItemByName("Password", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Password = oldUser.Password
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Email != newUser.Email {
|
if oldUser.Email != newUser.Email {
|
||||||
item := GetAccountItemByName("Email", organization)
|
item := GetAccountItemByName("Email", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Email = oldUser.Email
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Phone != newUser.Phone {
|
if oldUser.Phone != newUser.Phone {
|
||||||
item := GetAccountItemByName("Phone", organization)
|
item := GetAccountItemByName("Phone", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Phone = oldUser.Phone
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.CountryCode != newUser.CountryCode {
|
if oldUser.CountryCode != newUser.CountryCode {
|
||||||
item := GetAccountItemByName("Country code", organization)
|
item := GetAccountItemByName("Country code", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.CountryCode = oldUser.CountryCode
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Region != newUser.Region {
|
if oldUser.Region != newUser.Region {
|
||||||
item := GetAccountItemByName("Country/Region", organization)
|
item := GetAccountItemByName("Country/Region", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Region = oldUser.Region
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Location != newUser.Location {
|
if oldUser.Location != newUser.Location {
|
||||||
item := GetAccountItemByName("Location", organization)
|
item := GetAccountItemByName("Location", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Location = oldUser.Location
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Affiliation != newUser.Affiliation {
|
if oldUser.Affiliation != newUser.Affiliation {
|
||||||
item := GetAccountItemByName("Affiliation", organization)
|
item := GetAccountItemByName("Affiliation", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Affiliation = oldUser.Affiliation
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Title != newUser.Title {
|
if oldUser.Title != newUser.Title {
|
||||||
item := GetAccountItemByName("Title", organization)
|
item := GetAccountItemByName("Title", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Title = oldUser.Title
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Homepage != newUser.Homepage {
|
if oldUser.Homepage != newUser.Homepage {
|
||||||
item := GetAccountItemByName("Homepage", organization)
|
item := GetAccountItemByName("Homepage", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Homepage = oldUser.Homepage
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Bio != newUser.Bio {
|
if oldUser.Bio != newUser.Bio {
|
||||||
item := GetAccountItemByName("Bio", organization)
|
item := GetAccountItemByName("Bio", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Bio = oldUser.Bio
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Tag != newUser.Tag {
|
if oldUser.Tag != newUser.Tag {
|
||||||
item := GetAccountItemByName("Tag", organization)
|
item := GetAccountItemByName("Tag", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Tag = oldUser.Tag
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.SignupApplication != newUser.SignupApplication {
|
if oldUser.SignupApplication != newUser.SignupApplication {
|
||||||
item := GetAccountItemByName("Signup application", organization)
|
item := GetAccountItemByName("Signup application", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.SignupApplication = oldUser.SignupApplication
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Gender != newUser.Gender {
|
if oldUser.Gender != newUser.Gender {
|
||||||
item := GetAccountItemByName("Gender", organization)
|
item := GetAccountItemByName("Gender", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Gender = oldUser.Gender
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Birthday != newUser.Birthday {
|
if oldUser.Birthday != newUser.Birthday {
|
||||||
item := GetAccountItemByName("Birthday", organization)
|
item := GetAccountItemByName("Birthday", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Birthday = oldUser.Birthday
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Education != newUser.Education {
|
if oldUser.Education != newUser.Education {
|
||||||
item := GetAccountItemByName("Education", organization)
|
item := GetAccountItemByName("Education", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Education = oldUser.Education
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.IdCard != newUser.IdCard {
|
if oldUser.IdCard != newUser.IdCard {
|
||||||
item := GetAccountItemByName("ID card", organization)
|
item := GetAccountItemByName("ID card", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.IdCard = oldUser.IdCard
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.IdCardType != newUser.IdCardType {
|
if oldUser.IdCardType != newUser.IdCardType {
|
||||||
item := GetAccountItemByName("ID card type", organization)
|
item := GetAccountItemByName("ID card type", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.IdCardType = oldUser.IdCardType
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
oldUserPropertiesJson, _ := json.Marshal(oldUser.Properties)
|
oldUserPropertiesJson, _ := json.Marshal(oldUser.Properties)
|
||||||
newUserPropertiesJson, _ := json.Marshal(newUser.Properties)
|
newUserPropertiesJson, _ := json.Marshal(newUser.Properties)
|
||||||
if string(oldUserPropertiesJson) != string(newUserPropertiesJson) {
|
if string(oldUserPropertiesJson) != string(newUserPropertiesJson) {
|
||||||
item := GetAccountItemByName("Properties", organization)
|
item := GetAccountItemByName("Properties", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Properties = oldUser.Properties
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.PreferredMfaType != newUser.PreferredMfaType {
|
if oldUser.PreferredMfaType != newUser.PreferredMfaType {
|
||||||
item := GetAccountItemByName("Multi-factor authentication", organization)
|
item := GetAccountItemByName("Multi-factor authentication", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.PreferredMfaType = oldUser.PreferredMfaType
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Groups == nil {
|
if oldUser.Groups == nil {
|
||||||
@ -390,7 +490,11 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
newUserGroupsJson, _ := json.Marshal(newUser.Groups)
|
newUserGroupsJson, _ := json.Marshal(newUser.Groups)
|
||||||
if string(oldUserGroupsJson) != string(newUserGroupsJson) {
|
if string(oldUserGroupsJson) != string(newUserGroupsJson) {
|
||||||
item := GetAccountItemByName("Groups", organization)
|
item := GetAccountItemByName("Groups", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Groups = oldUser.Groups
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Address == nil {
|
if oldUser.Address == nil {
|
||||||
@ -404,65 +508,125 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
newUserAddressJson, _ := json.Marshal(newUser.Address)
|
newUserAddressJson, _ := json.Marshal(newUser.Address)
|
||||||
if string(oldUserAddressJson) != string(newUserAddressJson) {
|
if string(oldUserAddressJson) != string(newUserAddressJson) {
|
||||||
item := GetAccountItemByName("Address", organization)
|
item := GetAccountItemByName("Address", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Address = oldUser.Address
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if newUser.FaceIds != nil {
|
if newUser.FaceIds != nil {
|
||||||
item := GetAccountItemByName("Face ID", organization)
|
item := GetAccountItemByName("Face ID", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.FaceIds = oldUser.FaceIds
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.IsAdmin != newUser.IsAdmin {
|
if oldUser.IsAdmin != newUser.IsAdmin {
|
||||||
item := GetAccountItemByName("Is admin", organization)
|
item := GetAccountItemByName("Is admin", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.IsAdmin = oldUser.IsAdmin
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.IsForbidden != newUser.IsForbidden {
|
if oldUser.IsForbidden != newUser.IsForbidden {
|
||||||
item := GetAccountItemByName("Is forbidden", organization)
|
item := GetAccountItemByName("Is forbidden", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.IsForbidden = oldUser.IsForbidden
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.IsDeleted != newUser.IsDeleted {
|
if oldUser.IsDeleted != newUser.IsDeleted {
|
||||||
item := GetAccountItemByName("Is deleted", organization)
|
item := GetAccountItemByName("Is deleted", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.IsDeleted = oldUser.IsDeleted
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.NeedUpdatePassword != newUser.NeedUpdatePassword {
|
if oldUser.NeedUpdatePassword != newUser.NeedUpdatePassword {
|
||||||
item := GetAccountItemByName("Need update password", organization)
|
item := GetAccountItemByName("Need update password", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.NeedUpdatePassword = oldUser.NeedUpdatePassword
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if oldUser.IpWhitelist != newUser.IpWhitelist {
|
||||||
|
item := GetAccountItemByName("IP whitelist", organization)
|
||||||
|
if item == nil {
|
||||||
|
newUser.IpWhitelist = oldUser.IpWhitelist
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Balance != newUser.Balance {
|
if oldUser.Balance != newUser.Balance {
|
||||||
item := GetAccountItemByName("Balance", organization)
|
item := GetAccountItemByName("Balance", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Balance = oldUser.Balance
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Score != newUser.Score {
|
if oldUser.Score != newUser.Score {
|
||||||
item := GetAccountItemByName("Score", organization)
|
item := GetAccountItemByName("Score", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Score = oldUser.Score
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Karma != newUser.Karma {
|
if oldUser.Karma != newUser.Karma {
|
||||||
item := GetAccountItemByName("Karma", organization)
|
item := GetAccountItemByName("Karma", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Karma = oldUser.Karma
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Language != newUser.Language {
|
if oldUser.Language != newUser.Language {
|
||||||
item := GetAccountItemByName("Language", organization)
|
item := GetAccountItemByName("Language", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Language = oldUser.Language
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Ranking != newUser.Ranking {
|
if oldUser.Ranking != newUser.Ranking {
|
||||||
item := GetAccountItemByName("Ranking", organization)
|
item := GetAccountItemByName("Ranking", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Ranking = oldUser.Ranking
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Currency != newUser.Currency {
|
if oldUser.Currency != newUser.Currency {
|
||||||
item := GetAccountItemByName("Currency", organization)
|
item := GetAccountItemByName("Currency", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Currency = oldUser.Currency
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Hash != newUser.Hash {
|
if oldUser.Hash != newUser.Hash {
|
||||||
item := GetAccountItemByName("Hash", organization)
|
item := GetAccountItemByName("Hash", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Hash = oldUser.Hash
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, accountItem := range itemsChanged {
|
for _, accountItem := range itemsChanged {
|
||||||
|
@ -166,19 +166,76 @@ func AddToVerificationRecord(user *User, provider *Provider, remoteAddr, recordT
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func filterRecordIn24Hours(record *VerificationRecord) *VerificationRecord {
|
||||||
|
if record == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
|
if now-record.Time > 60*60*24 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return record
|
||||||
|
}
|
||||||
|
|
||||||
func getVerificationRecord(dest string) (*VerificationRecord, error) {
|
func getVerificationRecord(dest string) (*VerificationRecord, error) {
|
||||||
var record VerificationRecord
|
record := &VerificationRecord{}
|
||||||
record.Receiver = dest
|
record.Receiver = dest
|
||||||
|
|
||||||
has, err := ormer.Engine.Desc("time").Where("is_used = false").Get(&record)
|
has, err := ormer.Engine.Desc("time").Where("is_used = false").Get(record)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
record = filterRecordIn24Hours(record)
|
||||||
|
if record == nil {
|
||||||
|
has = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !has {
|
||||||
|
record = &VerificationRecord{}
|
||||||
|
record.Receiver = dest
|
||||||
|
|
||||||
|
has, err = ormer.Engine.Desc("time").Get(record)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
record = filterRecordIn24Hours(record)
|
||||||
|
if record == nil {
|
||||||
|
has = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !has {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return record, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return record, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUnusedVerificationRecord(dest string) (*VerificationRecord, error) {
|
||||||
|
record := &VerificationRecord{}
|
||||||
|
record.Receiver = dest
|
||||||
|
|
||||||
|
has, err := ormer.Engine.Desc("time").Where("is_used = false").Get(record)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
record = filterRecordIn24Hours(record)
|
||||||
|
if record == nil {
|
||||||
|
has = false
|
||||||
|
}
|
||||||
|
|
||||||
if !has {
|
if !has {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return &record, nil
|
return record, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckVerificationCode(dest string, code string, lang string) (*VerifyResult, error) {
|
func CheckVerificationCode(dest string, code string, lang string) (*VerifyResult, error) {
|
||||||
@ -187,7 +244,9 @@ func CheckVerificationCode(dest string, code string, lang string) (*VerifyResult
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if record == nil {
|
if record == nil {
|
||||||
return &VerifyResult{noRecordError, i18n.Translate(lang, "verification:The verification code has not been sent yet, or has already been used!")}, nil
|
return &VerifyResult{noRecordError, i18n.Translate(lang, "verification:The verification code has not been sent yet!")}, nil
|
||||||
|
} else if record.IsUsed {
|
||||||
|
return &VerifyResult{noRecordError, i18n.Translate(lang, "verification:The verification code has already been used!")}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
timeoutInMinutes, err := conf.GetConfigInt64("verificationCodeTimeout")
|
timeoutInMinutes, err := conf.GetConfigInt64("verificationCodeTimeout")
|
||||||
@ -196,9 +255,6 @@ func CheckVerificationCode(dest string, code string, lang string) (*VerifyResult
|
|||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
if now-record.Time > timeoutInMinutes*60*10 {
|
|
||||||
return &VerifyResult{noRecordError, i18n.Translate(lang, "verification:The verification code has not been sent yet!")}, nil
|
|
||||||
}
|
|
||||||
if now-record.Time > timeoutInMinutes*60 {
|
if now-record.Time > timeoutInMinutes*60 {
|
||||||
return &VerifyResult{timeoutError, fmt.Sprintf(i18n.Translate(lang, "verification:You should verify your code in %d min!"), timeoutInMinutes)}, nil
|
return &VerifyResult{timeoutError, fmt.Sprintf(i18n.Translate(lang, "verification:You should verify your code in %d min!"), timeoutInMinutes)}, nil
|
||||||
}
|
}
|
||||||
@ -211,7 +267,7 @@ func CheckVerificationCode(dest string, code string, lang string) (*VerifyResult
|
|||||||
}
|
}
|
||||||
|
|
||||||
func DisableVerificationCode(dest string) error {
|
func DisableVerificationCode(dest string) error {
|
||||||
record, err := getVerificationRecord(dest)
|
record, err := getUnusedVerificationRecord(dest)
|
||||||
if record == nil || err != nil {
|
if record == nil || err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ func getSubject(ctx *context.Context) (string, string) {
|
|||||||
return util.GetOwnerAndNameFromId(username)
|
return util.GetOwnerAndNameFromId(username)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getObject(ctx *context.Context) (string, string) {
|
func getObject(ctx *context.Context) (string, string, error) {
|
||||||
method := ctx.Request.Method
|
method := ctx.Request.Method
|
||||||
path := ctx.Request.URL.Path
|
path := ctx.Request.URL.Path
|
||||||
|
|
||||||
@ -65,13 +65,13 @@ func getObject(ctx *context.Context) (string, string) {
|
|||||||
if ctx.Input.Query("id") == "/" {
|
if ctx.Input.Query("id") == "/" {
|
||||||
adapterId := ctx.Input.Query("adapterId")
|
adapterId := ctx.Input.Query("adapterId")
|
||||||
if adapterId != "" {
|
if adapterId != "" {
|
||||||
return util.GetOwnerAndNameFromIdNoCheck(adapterId)
|
return util.GetOwnerAndNameFromIdWithError(adapterId)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// query == "?id=built-in/admin"
|
// query == "?id=built-in/admin"
|
||||||
id := ctx.Input.Query("id")
|
id := ctx.Input.Query("id")
|
||||||
if id != "" {
|
if id != "" {
|
||||||
return util.GetOwnerAndNameFromIdNoCheck(id)
|
return util.GetOwnerAndNameFromIdWithError(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -80,34 +80,34 @@ func getObject(ctx *context.Context) (string, string) {
|
|||||||
// query == "?id=built-in/admin"
|
// query == "?id=built-in/admin"
|
||||||
id := ctx.Input.Query("id")
|
id := ctx.Input.Query("id")
|
||||||
if id != "" {
|
if id != "" {
|
||||||
return util.GetOwnerAndNameFromIdNoCheck(id)
|
return util.GetOwnerAndNameFromIdWithError(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
owner := ctx.Input.Query("owner")
|
owner := ctx.Input.Query("owner")
|
||||||
if owner != "" {
|
if owner != "" {
|
||||||
return owner, ""
|
return owner, "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", ""
|
return "", "", nil
|
||||||
} else {
|
} else {
|
||||||
if path == "/api/add-policy" || path == "/api/remove-policy" || path == "/api/update-policy" {
|
if path == "/api/add-policy" || path == "/api/remove-policy" || path == "/api/update-policy" {
|
||||||
id := ctx.Input.Query("id")
|
id := ctx.Input.Query("id")
|
||||||
if id != "" {
|
if id != "" {
|
||||||
return util.GetOwnerAndNameFromIdNoCheck(id)
|
return util.GetOwnerAndNameFromIdWithError(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body := ctx.Input.RequestBody
|
body := ctx.Input.RequestBody
|
||||||
if len(body) == 0 {
|
if len(body) == 0 {
|
||||||
return ctx.Request.Form.Get("owner"), ctx.Request.Form.Get("name")
|
return ctx.Request.Form.Get("owner"), ctx.Request.Form.Get("name"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var obj Object
|
var obj Object
|
||||||
err := json.Unmarshal(body, &obj)
|
err := json.Unmarshal(body, &obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// panic(err)
|
// this is not error
|
||||||
return "", ""
|
return "", "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if path == "/api/delete-resource" {
|
if path == "/api/delete-resource" {
|
||||||
@ -117,7 +117,7 @@ func getObject(ctx *context.Context) (string, string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return obj.Owner, obj.Name
|
return obj.Owner, obj.Name, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,7 +183,12 @@ func ApiFilter(ctx *context.Context) {
|
|||||||
|
|
||||||
objOwner, objName := "", ""
|
objOwner, objName := "", ""
|
||||||
if urlPath != "/api/get-app-login" && urlPath != "/api/get-resource" {
|
if urlPath != "/api/get-app-login" && urlPath != "/api/get-resource" {
|
||||||
objOwner, objName = getObject(ctx)
|
var err error
|
||||||
|
objOwner, objName, err = getObject(ctx)
|
||||||
|
if err != nil {
|
||||||
|
responseError(ctx, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(urlPath, "/api/notify-payment") {
|
if strings.HasPrefix(urlPath, "/api/notify-payment") {
|
||||||
|
@ -16,6 +16,7 @@ package routers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/beego/beego/context"
|
"github.com/beego/beego/context"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
@ -23,6 +24,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func AutoSigninFilter(ctx *context.Context) {
|
func AutoSigninFilter(ctx *context.Context) {
|
||||||
|
urlPath := ctx.Request.URL.Path
|
||||||
|
if strings.HasPrefix(urlPath, "/api/login/oauth/access_token") {
|
||||||
|
return
|
||||||
|
}
|
||||||
//if getSessionUser(ctx) != "" {
|
//if getSessionUser(ctx) != "" {
|
||||||
// return
|
// return
|
||||||
//}
|
//}
|
||||||
@ -67,6 +72,17 @@ func AutoSigninFilter(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
accessKey := ctx.Input.Query("accessKey")
|
||||||
|
accessSecret := ctx.Input.Query("accessSecret")
|
||||||
|
if accessKey != "" && accessSecret != "" {
|
||||||
|
userId, err := getUsernameByKeys(ctx)
|
||||||
|
if err != nil {
|
||||||
|
responseError(ctx, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
setSessionUser(ctx, userId)
|
||||||
|
}
|
||||||
|
|
||||||
// "/page?clientId=123&clientSecret=456"
|
// "/page?clientId=123&clientSecret=456"
|
||||||
userId, err := getUsernameByClientIdSecret(ctx)
|
userId, err := getUsernameByClientIdSecret(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -21,7 +21,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/beego/beego/context"
|
"github.com/beego/beego/context"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/conf"
|
"github.com/casdoor/casdoor/conf"
|
||||||
"github.com/casdoor/casdoor/i18n"
|
"github.com/casdoor/casdoor/i18n"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
@ -126,7 +125,7 @@ func setSessionUser(ctx *context.Context, user string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/beego/beego/issues/3445#issuecomment-455411915
|
// https://github.com/beego/beego/issues/3445#issuecomment-455411915
|
||||||
ctx.Input.CruSession.SessionReleaseIfPresent(ctx.ResponseWriter)
|
ctx.Input.CruSession.SessionRelease(ctx.ResponseWriter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setSessionExpire(ctx *context.Context, ExpireTime int64) {
|
func setSessionExpire(ctx *context.Context, ExpireTime int64) {
|
||||||
@ -135,7 +134,7 @@ func setSessionExpire(ctx *context.Context, ExpireTime int64) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
ctx.Input.CruSession.SessionReleaseIfPresent(ctx.ResponseWriter)
|
ctx.Input.CruSession.SessionRelease(ctx.ResponseWriter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setSessionOidc(ctx *context.Context, scope string, aud string) {
|
func setSessionOidc(ctx *context.Context, scope string, aud string) {
|
||||||
@ -147,7 +146,7 @@ func setSessionOidc(ctx *context.Context, scope string, aud string) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
ctx.Input.CruSession.SessionReleaseIfPresent(ctx.ResponseWriter)
|
ctx.Input.CruSession.SessionRelease(ctx.ResponseWriter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseBearerToken(ctx *context.Context) string {
|
func parseBearerToken(ctx *context.Context) string {
|
||||||
|
@ -16,11 +16,11 @@ package routers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/beego/beego/context"
|
"github.com/beego/beego/context"
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -48,7 +48,17 @@ func CorsFilter(ctx *context.Context) {
|
|||||||
originHostname := getHostname(origin)
|
originHostname := getHostname(origin)
|
||||||
host := removePort(ctx.Request.Host)
|
host := removePort(ctx.Request.Host)
|
||||||
|
|
||||||
if strings.HasPrefix(origin, "http://localhost") || strings.HasPrefix(origin, "https://localhost") || strings.HasPrefix(origin, "http://127.0.0.1") || strings.HasPrefix(origin, "http://casdoor-app") || strings.Contains(origin, ".chromiumapp.org") {
|
if origin == "null" {
|
||||||
|
origin = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
isValid, err := util.IsValidOrigin(origin)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ResponseWriter.WriteHeader(http.StatusForbidden)
|
||||||
|
responseError(ctx, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if isValid {
|
||||||
setCorsHeaders(ctx, origin)
|
setCorsHeaders(ctx, origin)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -174,6 +174,8 @@ func initAPI() {
|
|||||||
beego.Router("/api/get-all-actions", &controllers.ApiController{}, "GET:GetAllActions")
|
beego.Router("/api/get-all-actions", &controllers.ApiController{}, "GET:GetAllActions")
|
||||||
beego.Router("/api/get-all-roles", &controllers.ApiController{}, "GET:GetAllRoles")
|
beego.Router("/api/get-all-roles", &controllers.ApiController{}, "GET:GetAllRoles")
|
||||||
|
|
||||||
|
beego.Router("/api/run-casbin-command", &controllers.ApiController{}, "GET:RunCasbinCommand")
|
||||||
|
|
||||||
beego.Router("/api/get-sessions", &controllers.ApiController{}, "GET:GetSessions")
|
beego.Router("/api/get-sessions", &controllers.ApiController{}, "GET:GetSessions")
|
||||||
beego.Router("/api/get-session", &controllers.ApiController{}, "GET:GetSingleSession")
|
beego.Router("/api/get-session", &controllers.ApiController{}, "GET:GetSingleSession")
|
||||||
beego.Router("/api/update-session", &controllers.ApiController{}, "POST:UpdateSession")
|
beego.Router("/api/update-session", &controllers.ApiController{}, "POST:UpdateSession")
|
||||||
@ -290,6 +292,7 @@ func initAPI() {
|
|||||||
|
|
||||||
beego.Router("/.well-known/openid-configuration", &controllers.RootController{}, "GET:GetOidcDiscovery")
|
beego.Router("/.well-known/openid-configuration", &controllers.RootController{}, "GET:GetOidcDiscovery")
|
||||||
beego.Router("/.well-known/jwks", &controllers.RootController{}, "*:GetJwks")
|
beego.Router("/.well-known/jwks", &controllers.RootController{}, "*:GetJwks")
|
||||||
|
beego.Router("/.well-known/webfinger", &controllers.RootController{}, "GET:GetWebFinger")
|
||||||
|
|
||||||
beego.Router("/cas/:organization/:application/serviceValidate", &controllers.RootController{}, "GET:CasServiceValidate")
|
beego.Router("/cas/:organization/:application/serviceValidate", &controllers.RootController{}, "GET:CasServiceValidate")
|
||||||
beego.Router("/cas/:organization/:application/proxyValidate", &controllers.RootController{}, "GET:CasProxyValidate")
|
beego.Router("/cas/:organization/:application/proxyValidate", &controllers.RootController{}, "GET:CasProxyValidate")
|
||||||
|
@ -43,6 +43,10 @@ func getWebBuildFolder() string {
|
|||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if util.FileExist(filepath.Join(frontendBaseDir, "index.html")) {
|
||||||
|
return frontendBaseDir
|
||||||
|
}
|
||||||
|
|
||||||
path = filepath.Join(frontendBaseDir, "web/build")
|
path = filepath.Join(frontendBaseDir, "web/build")
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
@ -58,7 +62,7 @@ func fastAutoSignin(ctx *context.Context) (string, error) {
|
|||||||
redirectUri := ctx.Input.Query("redirect_uri")
|
redirectUri := ctx.Input.Query("redirect_uri")
|
||||||
scope := ctx.Input.Query("scope")
|
scope := ctx.Input.Query("scope")
|
||||||
state := ctx.Input.Query("state")
|
state := ctx.Input.Query("state")
|
||||||
nonce := ""
|
nonce := ctx.Input.Query("nonce")
|
||||||
codeChallenge := ctx.Input.Query("code_challenge")
|
codeChallenge := ctx.Input.Query("code_challenge")
|
||||||
if clientId == "" || responseType != "code" || redirectUri == "" {
|
if clientId == "" || responseType != "code" || redirectUri == "" {
|
||||||
return "", nil
|
return "", nil
|
||||||
|
64
routers/timeout_filter.go
Normal file
64
routers/timeout_filter.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// Copyright 2024 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 routers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/beego/beego/context"
|
||||||
|
"github.com/casdoor/casdoor/conf"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
inactiveTimeoutMinutes int64
|
||||||
|
requestTimeMap sync.Map
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var err error
|
||||||
|
inactiveTimeoutMinutes, err = conf.GetConfigInt64("inactiveTimeoutMinutes")
|
||||||
|
if err != nil {
|
||||||
|
inactiveTimeoutMinutes = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func timeoutLogout(ctx *context.Context, sessionId string) {
|
||||||
|
requestTimeMap.Delete(sessionId)
|
||||||
|
ctx.Input.CruSession.Set("username", "")
|
||||||
|
ctx.Input.CruSession.Set("accessToken", "")
|
||||||
|
ctx.Input.CruSession.Delete("SessionData")
|
||||||
|
responseError(ctx, fmt.Sprintf(T(ctx, "auth:Timeout for inactivity of %d minutes"), inactiveTimeoutMinutes))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TimeoutFilter(ctx *context.Context) {
|
||||||
|
if inactiveTimeoutMinutes <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
owner, name := getSubject(ctx)
|
||||||
|
if owner == "anonymous" || name == "anonymous" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionId := ctx.Input.CruSession.SessionID()
|
||||||
|
currentTime := time.Now()
|
||||||
|
preRequestTime, has := requestTimeMap.Load(sessionId)
|
||||||
|
requestTimeMap.Store(sessionId, currentTime)
|
||||||
|
if has && preRequestTime.(time.Time).Add(time.Minute*time.Duration(inactiveTimeoutMinutes)).Before(currentTime) {
|
||||||
|
timeoutLogout(ctx, sessionId)
|
||||||
|
}
|
||||||
|
}
|
19
storage/casdoor.go
Normal file
19
storage/casdoor.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/casdoor/oss"
|
||||||
|
"github.com/casdoor/oss/casdoor"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewCasdoorStorageProvider(providerType string, clientId string, clientSecret string, region string, bucket string, endpoint string, cert string, content string) oss.StorageInterface {
|
||||||
|
sp := casdoor.New(&casdoor.Config{
|
||||||
|
clientId,
|
||||||
|
clientSecret,
|
||||||
|
endpoint,
|
||||||
|
cert,
|
||||||
|
region,
|
||||||
|
content,
|
||||||
|
bucket,
|
||||||
|
})
|
||||||
|
return sp
|
||||||
|
}
|
21
storage/cucloud_oss.go
Normal file
21
storage/cucloud_oss.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
awss3 "github.com/aws/aws-sdk-go/service/s3"
|
||||||
|
"github.com/casdoor/oss"
|
||||||
|
"github.com/casdoor/oss/s3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewCUCloudOssStorageProvider(clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
|
||||||
|
sp := s3.New(&s3.Config{
|
||||||
|
AccessID: clientId,
|
||||||
|
AccessKey: clientSecret,
|
||||||
|
Region: region,
|
||||||
|
Bucket: bucket,
|
||||||
|
Endpoint: endpoint,
|
||||||
|
S3Endpoint: endpoint,
|
||||||
|
ACL: awss3.BucketCannedACLPublicRead,
|
||||||
|
})
|
||||||
|
|
||||||
|
return sp
|
||||||
|
}
|
@ -16,7 +16,7 @@ package storage
|
|||||||
|
|
||||||
import "github.com/casdoor/oss"
|
import "github.com/casdoor/oss"
|
||||||
|
|
||||||
func GetStorageProvider(providerType string, clientId string, clientSecret string, region string, bucket string, endpoint string) (oss.StorageInterface, error) {
|
func GetStorageProvider(providerType string, clientId string, clientSecret string, region string, bucket string, endpoint string, cert string, content string) (oss.StorageInterface, error) {
|
||||||
switch providerType {
|
switch providerType {
|
||||||
case "Local File System":
|
case "Local File System":
|
||||||
return NewLocalFileSystemStorageProvider(), nil
|
return NewLocalFileSystemStorageProvider(), nil
|
||||||
@ -36,6 +36,10 @@ func GetStorageProvider(providerType string, clientId string, clientSecret strin
|
|||||||
return NewGoogleCloudStorageProvider(clientSecret, bucket, endpoint), nil
|
return NewGoogleCloudStorageProvider(clientSecret, bucket, endpoint), nil
|
||||||
case "Synology":
|
case "Synology":
|
||||||
return NewSynologyNasStorageProvider(clientId, clientSecret, endpoint), nil
|
return NewSynologyNasStorageProvider(clientId, clientSecret, endpoint), nil
|
||||||
|
case "Casdoor":
|
||||||
|
return NewCasdoorStorageProvider(providerType, clientId, clientSecret, region, bucket, endpoint, cert, content), nil
|
||||||
|
case "CUCloud OSS":
|
||||||
|
return NewCUCloudOssStorageProvider(clientId, clientSecret, region, bucket, endpoint), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
48
util/log.go
48
util/log.go
@ -23,50 +23,50 @@ import (
|
|||||||
"github.com/beego/beego/logs"
|
"github.com/beego/beego/logs"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetIPInfo(clientIP string) string {
|
func getIpInfo(clientIp string) string {
|
||||||
if clientIP == "" {
|
if clientIp == "" {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
ips := strings.Split(clientIP, ",")
|
ips := strings.Split(clientIp, ",")
|
||||||
res := ""
|
res := strings.TrimSpace(ips[0])
|
||||||
for i := range ips {
|
//res := ""
|
||||||
ip := strings.TrimSpace(ips[i])
|
//for i := range ips {
|
||||||
// desc := GetDescFromIP(ip)
|
// ip := strings.TrimSpace(ips[i])
|
||||||
ipstr := fmt.Sprintf("%s: %s", ip, "")
|
// ipstr := fmt.Sprintf("%s: %s", ip, "")
|
||||||
if i != len(ips)-1 {
|
// if i != len(ips)-1 {
|
||||||
res += ipstr + " -> "
|
// res += ipstr + " -> "
|
||||||
} else {
|
// } else {
|
||||||
res += ipstr
|
// res += ipstr
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetIPFromRequest(req *http.Request) string {
|
func GetClientIpFromRequest(req *http.Request) string {
|
||||||
clientIP := req.Header.Get("x-forwarded-for")
|
clientIp := req.Header.Get("x-forwarded-for")
|
||||||
if clientIP == "" {
|
if clientIp == "" {
|
||||||
ipPort := strings.Split(req.RemoteAddr, ":")
|
ipPort := strings.Split(req.RemoteAddr, ":")
|
||||||
if len(ipPort) >= 1 && len(ipPort) <= 2 {
|
if len(ipPort) >= 1 && len(ipPort) <= 2 {
|
||||||
clientIP = ipPort[0]
|
clientIp = ipPort[0]
|
||||||
} else if len(ipPort) > 2 {
|
} else if len(ipPort) > 2 {
|
||||||
idx := strings.LastIndex(req.RemoteAddr, ":")
|
idx := strings.LastIndex(req.RemoteAddr, ":")
|
||||||
clientIP = req.RemoteAddr[0:idx]
|
clientIp = req.RemoteAddr[0:idx]
|
||||||
clientIP = strings.TrimLeft(clientIP, "[")
|
clientIp = strings.TrimLeft(clientIp, "[")
|
||||||
clientIP = strings.TrimRight(clientIP, "]")
|
clientIp = strings.TrimRight(clientIp, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return GetIPInfo(clientIP)
|
return getIpInfo(clientIp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func LogInfo(ctx *context.Context, f string, v ...interface{}) {
|
func LogInfo(ctx *context.Context, f string, v ...interface{}) {
|
||||||
ipString := fmt.Sprintf("(%s) ", GetIPFromRequest(ctx.Request))
|
ipString := fmt.Sprintf("(%s) ", GetClientIpFromRequest(ctx.Request))
|
||||||
logs.Info(ipString+f, v...)
|
logs.Info(ipString+f, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func LogWarning(ctx *context.Context, f string, v ...interface{}) {
|
func LogWarning(ctx *context.Context, f string, v ...interface{}) {
|
||||||
ipString := fmt.Sprintf("(%s) ", GetIPFromRequest(ctx.Request))
|
ipString := fmt.Sprintf("(%s) ", GetClientIpFromRequest(ctx.Request))
|
||||||
logs.Warning(ipString+f, v...)
|
logs.Warning(ipString+f, v...)
|
||||||
}
|
}
|
||||||
|
76
util/obfuscator.go
Normal file
76
util/obfuscator.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
// Copyright 2024 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/des"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func unPaddingPkcs7(s []byte) []byte {
|
||||||
|
length := len(s)
|
||||||
|
if length == 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
unPadding := int(s[length-1])
|
||||||
|
return s[:(length - unPadding)]
|
||||||
|
}
|
||||||
|
|
||||||
|
func decryptDesOrAes(passwordCipher string, block cipher.Block) (string, error) {
|
||||||
|
passwordCipherBytes, err := hex.DecodeString(passwordCipher)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(passwordCipherBytes) < block.BlockSize() {
|
||||||
|
return "", fmt.Errorf("the password ciphertext should contain a random hexadecimal string of length %d at the beginning", block.BlockSize()*2)
|
||||||
|
}
|
||||||
|
|
||||||
|
iv := passwordCipherBytes[:block.BlockSize()]
|
||||||
|
password := make([]byte, len(passwordCipherBytes)-block.BlockSize())
|
||||||
|
|
||||||
|
mode := cipher.NewCBCDecrypter(block, iv)
|
||||||
|
mode.CryptBlocks(password, passwordCipherBytes[block.BlockSize():])
|
||||||
|
|
||||||
|
return string(unPaddingPkcs7(password)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUnobfuscatedPassword(passwordObfuscatorType string, passwordObfuscatorKey string, passwordCipher string) (string, error) {
|
||||||
|
if passwordObfuscatorType == "Plain" || passwordObfuscatorType == "" {
|
||||||
|
return passwordCipher, nil
|
||||||
|
} else if passwordObfuscatorType == "DES" || passwordObfuscatorType == "AES" {
|
||||||
|
key, err := hex.DecodeString(passwordObfuscatorKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var block cipher.Block
|
||||||
|
if passwordObfuscatorType == "DES" {
|
||||||
|
block, err = des.NewCipher(key)
|
||||||
|
} else {
|
||||||
|
block, err = aes.NewCipher(key)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return decryptDesOrAes(passwordCipher, block)
|
||||||
|
} else {
|
||||||
|
return "", fmt.Errorf("unsupported password obfuscator type: %s", passwordObfuscatorType)
|
||||||
|
}
|
||||||
|
}
|
@ -131,6 +131,15 @@ func GetOwnerAndNameFromId(id string) (string, string) {
|
|||||||
return tokens[0], tokens[1]
|
return tokens[0], tokens[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetOwnerAndNameFromIdWithError(id string) (string, string, error) {
|
||||||
|
tokens := strings.Split(id, "/")
|
||||||
|
if len(tokens) != 2 {
|
||||||
|
return "", "", errors.New("GetOwnerAndNameFromId() error, wrong token count for ID: " + id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokens[0], tokens[1], nil
|
||||||
|
}
|
||||||
|
|
||||||
func GetOwnerFromId(id string) string {
|
func GetOwnerFromId(id string) string {
|
||||||
tokens := strings.Split(id, "/")
|
tokens := strings.Split(id, "/")
|
||||||
if len(tokens) != 2 {
|
if len(tokens) != 2 {
|
||||||
@ -154,6 +163,16 @@ func GetOwnerAndNameAndOtherFromId(id string) (string, string, string) {
|
|||||||
return tokens[0], tokens[1], tokens[2]
|
return tokens[0], tokens[1], tokens[2]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetSharedOrgFromApp(rawName string) (name string, organization string) {
|
||||||
|
name = rawName
|
||||||
|
splitName := strings.Split(rawName, "-org-")
|
||||||
|
if len(splitName) >= 2 {
|
||||||
|
organization = splitName[len(splitName)-1]
|
||||||
|
name = splitName[0]
|
||||||
|
}
|
||||||
|
return name, organization
|
||||||
|
}
|
||||||
|
|
||||||
func GenerateId() string {
|
func GenerateId() string {
|
||||||
return uuid.NewString()
|
return uuid.NewString()
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ package util
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/mail"
|
"net/mail"
|
||||||
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -24,10 +25,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
rePhone *regexp.Regexp
|
rePhone *regexp.Regexp
|
||||||
ReWhiteSpace *regexp.Regexp
|
ReWhiteSpace *regexp.Regexp
|
||||||
ReFieldWhiteList *regexp.Regexp
|
ReFieldWhiteList *regexp.Regexp
|
||||||
ReUserName *regexp.Regexp
|
ReUserName *regexp.Regexp
|
||||||
|
ReUserNameWithEmail *regexp.Regexp
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -35,6 +37,7 @@ func init() {
|
|||||||
ReWhiteSpace, _ = regexp.Compile(`\s`)
|
ReWhiteSpace, _ = regexp.Compile(`\s`)
|
||||||
ReFieldWhiteList, _ = regexp.Compile(`^[A-Za-z0-9]+$`)
|
ReFieldWhiteList, _ = regexp.Compile(`^[A-Za-z0-9]+$`)
|
||||||
ReUserName, _ = regexp.Compile("^[a-zA-Z0-9]+([-._][a-zA-Z0-9]+)*$")
|
ReUserName, _ = regexp.Compile("^[a-zA-Z0-9]+([-._][a-zA-Z0-9]+)*$")
|
||||||
|
ReUserNameWithEmail, _ = regexp.Compile(`^([a-zA-Z0-9]+([-._][a-zA-Z0-9]+)*)|([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})$`) // Add support for email formats
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsEmailValid(email string) bool {
|
func IsEmailValid(email string) bool {
|
||||||
@ -51,6 +54,9 @@ func IsPhoneValid(phone string, countryCode string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func IsPhoneAllowInRegin(countryCode string, allowRegions []string) bool {
|
func IsPhoneAllowInRegin(countryCode string, allowRegions []string) bool {
|
||||||
|
if ContainsString(allowRegions, "All") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
return ContainsString(allowRegions, countryCode)
|
return ContainsString(allowRegions, countryCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,3 +103,21 @@ func GetCountryCode(prefix string, phone string) (string, error) {
|
|||||||
func FilterField(field string) bool {
|
func FilterField(field string) bool {
|
||||||
return ReFieldWhiteList.MatchString(field)
|
return ReFieldWhiteList.MatchString(field)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsValidOrigin(origin string) (bool, error) {
|
||||||
|
urlObj, err := url.Parse(origin)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if urlObj == nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
originHostOnly := ""
|
||||||
|
if urlObj.Host != "" {
|
||||||
|
originHostOnly = fmt.Sprintf("%s://%s", urlObj.Scheme, urlObj.Hostname())
|
||||||
|
}
|
||||||
|
|
||||||
|
res := originHostOnly == "http://localhost" || originHostOnly == "https://localhost" || originHostOnly == "http://127.0.0.1" || originHostOnly == "http://casdoor-app" || strings.HasSuffix(originHostOnly, ".chromiumapp.org")
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
"copy-to-clipboard": "^3.3.1",
|
"copy-to-clipboard": "^3.3.1",
|
||||||
"core-js": "^3.25.0",
|
"core-js": "^3.25.0",
|
||||||
"craco-less": "^2.0.0",
|
"craco-less": "^2.0.0",
|
||||||
|
"crypto-js": "^4.2.0",
|
||||||
"echarts": "^5.4.3",
|
"echarts": "^5.4.3",
|
||||||
"ethers": "5.6.9",
|
"ethers": "5.6.9",
|
||||||
"face-api.js": "^0.22.2",
|
"face-api.js": "^0.22.2",
|
||||||
|
@ -56,9 +56,11 @@ class AdapterListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -344,7 +344,8 @@ class App extends Component {
|
|||||||
window.location.pathname.startsWith("/cas") ||
|
window.location.pathname.startsWith("/cas") ||
|
||||||
window.location.pathname.startsWith("/select-plan") ||
|
window.location.pathname.startsWith("/select-plan") ||
|
||||||
window.location.pathname.startsWith("/buy-plan") ||
|
window.location.pathname.startsWith("/buy-plan") ||
|
||||||
window.location.pathname.startsWith("/qrcode") ;
|
window.location.pathname.startsWith("/qrcode") ||
|
||||||
|
window.location.pathname.startsWith("/captcha");
|
||||||
}
|
}
|
||||||
|
|
||||||
onClick = ({key}) => {
|
onClick = ({key}) => {
|
||||||
@ -361,7 +362,11 @@ class App extends Component {
|
|||||||
if (this.isDoorPages()) {
|
if (this.isDoorPages()) {
|
||||||
return (
|
return (
|
||||||
<ConfigProvider theme={{
|
<ConfigProvider theme={{
|
||||||
algorithm: Setting.getAlgorithm(["default"]),
|
token: {
|
||||||
|
colorPrimary: this.state.themeData.colorPrimary,
|
||||||
|
borderRadius: this.state.themeData.borderRadius,
|
||||||
|
},
|
||||||
|
algorithm: Setting.getAlgorithm(this.state.themeAlgorithm),
|
||||||
}}>
|
}}>
|
||||||
<StyleProvider hashPriority="high" transformers={[legacyLogicalPropertiesTransformer]}>
|
<StyleProvider hashPriority="high" transformers={[legacyLogicalPropertiesTransformer]}>
|
||||||
<Layout id="parent-area">
|
<Layout id="parent-area">
|
||||||
@ -371,6 +376,7 @@ class App extends Component {
|
|||||||
<EntryPage
|
<EntryPage
|
||||||
account={this.state.account}
|
account={this.state.account}
|
||||||
theme={this.state.themeData}
|
theme={this.state.themeData}
|
||||||
|
themeAlgorithm={this.state.themeAlgorithm}
|
||||||
updateApplication={(application) => {
|
updateApplication={(application) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
application: application,
|
application: application,
|
||||||
@ -445,7 +451,6 @@ class App extends Component {
|
|||||||
setLogoutState={() => {
|
setLogoutState={() => {
|
||||||
this.setState({
|
this.setState({
|
||||||
account: null,
|
account: null,
|
||||||
themeAlgorithm: ["default"],
|
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -129,6 +129,15 @@ img {
|
|||||||
background-attachment: fixed;
|
background-attachment: fixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loginBackgroundDark {
|
||||||
|
flex: auto;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background: #000 no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-attachment: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
.ant-menu-horizontal {
|
.ant-menu-horizontal {
|
||||||
border-bottom: none !important;
|
border-bottom: none !important;
|
||||||
}
|
}
|
||||||
|
@ -46,12 +46,18 @@ require("codemirror/mode/css/css");
|
|||||||
const {Option} = Select;
|
const {Option} = Select;
|
||||||
|
|
||||||
const template = `<style>
|
const template = `<style>
|
||||||
.login-panel{
|
.login-panel {
|
||||||
padding: 40px 70px 0 70px;
|
padding: 40px 70px 0 70px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
box-shadow: 0 0 30px 20px rgba(0, 0, 0, 0.20);
|
box-shadow: 0 0 30px 20px rgba(0, 0, 0, 0.20);
|
||||||
}
|
}
|
||||||
|
.login-panel-dark {
|
||||||
|
padding: 40px 70px 0 70px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: #333333;
|
||||||
|
box-shadow: 0 0 30px 20px rgba(255, 255, 255, 0.20);
|
||||||
|
}
|
||||||
</style>`;
|
</style>`;
|
||||||
|
|
||||||
const previewGrid = Setting.isMobile() ? 22 : 11;
|
const previewGrid = Setting.isMobile() ? 22 : 11;
|
||||||
@ -116,7 +122,6 @@ class ApplicationEditPage extends React.Component {
|
|||||||
UNSAFE_componentWillMount() {
|
UNSAFE_componentWillMount() {
|
||||||
this.getApplication();
|
this.getApplication();
|
||||||
this.getOrganizations();
|
this.getOrganizations();
|
||||||
this.getProviders();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getApplication() {
|
getApplication() {
|
||||||
@ -145,7 +150,9 @@ class ApplicationEditPage extends React.Component {
|
|||||||
application: application,
|
application: application,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.getCerts(application.organization);
|
this.getProviders(application);
|
||||||
|
|
||||||
|
this.getCerts(application);
|
||||||
|
|
||||||
this.getSamlMetadata(application.enableSamlPostBinding);
|
this.getSamlMetadata(application.enableSamlPostBinding);
|
||||||
});
|
});
|
||||||
@ -166,7 +173,11 @@ class ApplicationEditPage extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getCerts(owner) {
|
getCerts(application) {
|
||||||
|
let owner = application.organization;
|
||||||
|
if (application.isShared) {
|
||||||
|
owner = this.props.owner;
|
||||||
|
}
|
||||||
CertBackend.getCerts(owner)
|
CertBackend.getCerts(owner)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
@ -175,8 +186,12 @@ class ApplicationEditPage extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getProviders() {
|
getProviders(application) {
|
||||||
ProviderBackend.getProviders(this.state.owner)
|
let owner = application.organization;
|
||||||
|
if (application.isShared) {
|
||||||
|
owner = this.props.account.owner;
|
||||||
|
}
|
||||||
|
ProviderBackend.getProviders(owner)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
this.setState({
|
this.setState({
|
||||||
@ -263,6 +278,16 @@ 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("general:Is shared"), i18next.t("general:Is shared - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Switch disabled={Setting.isAdminUser()} checked={this.state.application.isShared} onChange={checked => {
|
||||||
|
this.updateApplicationField("isShared", checked);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("general:Logo"), i18next.t("general:Logo - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Logo"), i18next.t("general:Logo - Tooltip"))} :
|
||||||
@ -388,6 +413,16 @@ 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:Token signing method"), i18next.t("application:Token signing method - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: "100%"}} value={this.state.application.tokenSigningMethod === "" ? "RS256" : this.state.application.tokenSigningMethod} onChange={(value => {this.updateApplicationField("tokenSigningMethod", value);})}
|
||||||
|
options={["RS256", "RS512", "ES256", "ES512", "ES384"].map((item) => Setting.getOption(item, item))}
|
||||||
|
/>
|
||||||
|
</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("application:Token fields"), i18next.t("application:Token fields - Tooltip"))} :
|
{Setting.getLabel(i18next.t("application:Token fields"), i18next.t("application:Token fields - Tooltip"))} :
|
||||||
@ -563,6 +598,16 @@ 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("general:IP whitelist"), i18next.t("general:IP whitelist - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input placeholder = {this.state.application.organizationObj?.ipWhitelist} value={this.state.application.ipWhitelist} onChange={e => {
|
||||||
|
this.updateApplicationField("ipWhitelist", e.target.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("signup:Terms of Use"), i18next.t("signup:Terms of Use - Tooltip"))} :
|
{Setting.getLabel(i18next.t("signup:Terms of Use"), i18next.t("signup:Terms of Use - Tooltip"))} :
|
||||||
@ -674,6 +719,16 @@ class ApplicationEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}}>
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("application:Use Email as NameID"), i18next.t("application:Use Email as NameID - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={1}>
|
||||||
|
<Switch checked={this.state.application.useEmailAsSamlNameId} onChange={checked => {
|
||||||
|
this.updateApplicationField("useEmailAsSamlNameId", checked);
|
||||||
|
}} />
|
||||||
|
</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("application:Enable SAML POST binding"), i18next.t("application:Enable SAML POST binding - Tooltip"))} :
|
{Setting.getLabel(i18next.t("application:Enable SAML POST binding"), i18next.t("application:Enable SAML POST binding - Tooltip"))} :
|
||||||
@ -710,7 +765,7 @@ class ApplicationEditPage extends React.Component {
|
|||||||
/>
|
/>
|
||||||
<br />
|
<br />
|
||||||
<Button style={{marginBottom: "10px"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => {
|
<Button style={{marginBottom: "10px"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => {
|
||||||
copy(`${window.location.origin}/api/saml/metadata?application=admin/${encodeURIComponent(this.state.applicationName)}&post=${this.state.application.enableSamlPostBinding}`);
|
copy(`${window.location.origin}/api/saml/metadata?application=admin/${encodeURIComponent(this.state.applicationName)}&enablePostBinding=${this.state.application.enableSamlPostBinding}`);
|
||||||
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
|
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -938,6 +993,7 @@ class ApplicationEditPage extends React.Component {
|
|||||||
<SigninTable
|
<SigninTable
|
||||||
title={i18next.t("application:Signin items")}
|
title={i18next.t("application:Signin items")}
|
||||||
table={this.state.application.signinItems}
|
table={this.state.application.signinItems}
|
||||||
|
themeAlgorithm={this.state.themeAlgorithm}
|
||||||
onUpdateTable={(value) => {
|
onUpdateTable={(value) => {
|
||||||
this.updateApplicationField("signinItems", value);
|
this.updateApplicationField("signinItems", value);
|
||||||
}}
|
}}
|
||||||
@ -989,7 +1045,11 @@ class ApplicationEditPage extends React.Component {
|
|||||||
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'\"";
|
||||||
}
|
}
|
||||||
|
|
||||||
const signInUrl = `/login/oauth/authorize?client_id=${this.state.application.clientId}&response_type=code&redirect_uri=${redirectUri}&scope=read&state=casdoor`;
|
let clientId = this.state.application.clientId;
|
||||||
|
if (this.state.application.isShared) {
|
||||||
|
clientId += `-org-${this.props.account.owner}`;
|
||||||
|
}
|
||||||
|
const signInUrl = `/login/oauth/authorize?client_id=${clientId}&response_type=code&redirect_uri=${redirectUri}&scope=read&state=casdoor`;
|
||||||
const maskStyle = {position: "absolute", top: "0px", left: "0px", zIndex: 10, height: "97%", width: "100%", background: "rgba(0,0,0,0.4)"};
|
const maskStyle = {position: "absolute", top: "0px", left: "0px", zIndex: 10, height: "97%", width: "100%", background: "rgba(0,0,0,0.4)"};
|
||||||
if (!Setting.isPasswordEnabled(this.state.application)) {
|
if (!Setting.isPasswordEnabled(this.state.application)) {
|
||||||
signUpUrl = signInUrl.replace("/login/oauth/authorize", "/signup/oauth/authorize");
|
signUpUrl = signInUrl.replace("/login/oauth/authorize", "/signup/oauth/authorize");
|
||||||
|
@ -97,9 +97,11 @@ class ApplicationListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
@ -123,7 +125,7 @@ class ApplicationListPage extends BaseListPage {
|
|||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return (
|
return (
|
||||||
<Link to={`/applications/${record.organization}/${text}`}>
|
<Link to={`/applications/${record.organization}/${text}`}>
|
||||||
{text}
|
{Setting.getApplicationDisplayName(record)}
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
116
web/src/CaptchaPage.js
Normal file
116
web/src/CaptchaPage.js
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import {CaptchaModal} from "./common/modal/CaptchaModal";
|
||||||
|
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
||||||
|
import * as Setting from "./Setting";
|
||||||
|
|
||||||
|
class CaptchaPage extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
const params = new URLSearchParams(this.props.location.search);
|
||||||
|
this.state = {
|
||||||
|
owner: "admin",
|
||||||
|
application: null,
|
||||||
|
clientId: params.get("client_id"),
|
||||||
|
applicationName: params.get("state"),
|
||||||
|
redirectUri: params.get("redirect_uri"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.getApplication();
|
||||||
|
}
|
||||||
|
|
||||||
|
onUpdateApplication(application) {
|
||||||
|
this.setState({
|
||||||
|
application: application,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getApplication() {
|
||||||
|
if (this.state.applicationName === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplicationBackend.getApplication(this.state.owner, this.state.applicationName)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "error") {
|
||||||
|
this.onUpdateApplication(null);
|
||||||
|
this.setState({
|
||||||
|
msg: res.msg,
|
||||||
|
});
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
this.onUpdateApplication(res.data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getCaptchaProviderItems(application) {
|
||||||
|
const providers = application?.providers;
|
||||||
|
|
||||||
|
if (providers === undefined || providers === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return providers.filter(providerItem => {
|
||||||
|
if (providerItem.provider === undefined || providerItem.provider === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return providerItem.provider.category === "Captcha";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(values) {
|
||||||
|
Setting.goToLink(`${this.state.redirectUri}?code=${values.captchaToken}&type=${values.captchaType}&secret=${values.clientSecret}&applicationId=${values.applicationId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCaptchaModal(application) {
|
||||||
|
const captchaProviderItems = this.getCaptchaProviderItems(application);
|
||||||
|
if (captchaProviderItems === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const alwaysProviderItems = captchaProviderItems.filter(providerItem => providerItem.rule === "Always");
|
||||||
|
const dynamicProviderItems = captchaProviderItems.filter(providerItem => providerItem.rule === "Dynamic");
|
||||||
|
const provider = alwaysProviderItems.length > 0
|
||||||
|
? alwaysProviderItems[0].provider
|
||||||
|
: dynamicProviderItems[0].provider;
|
||||||
|
|
||||||
|
return <CaptchaModal
|
||||||
|
owner={provider.owner}
|
||||||
|
name={provider.name}
|
||||||
|
visible={true}
|
||||||
|
onOk={(captchaType, captchaToken, clientSecret) => {
|
||||||
|
const values = {
|
||||||
|
captchaType: captchaType,
|
||||||
|
captchaToken: captchaToken,
|
||||||
|
clientSecret: clientSecret,
|
||||||
|
applicationId: `${provider.owner}/${provider.name}`,
|
||||||
|
};
|
||||||
|
this.callback(values);
|
||||||
|
}}
|
||||||
|
onCancel={() => this.callback({captchaType: "none", captchaToken: "", clientSecret: ""})}
|
||||||
|
isCurrentProvider={true}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
this.renderCaptchaModal(this.state.application)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CaptchaPage;
|
97
web/src/CasbinEditor.js
Normal file
97
web/src/CasbinEditor.js
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
// Copyright 2024 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, {useCallback, useEffect, useRef, useState} from "react";
|
||||||
|
import {Controlled as CodeMirror} from "react-codemirror2";
|
||||||
|
import "codemirror/lib/codemirror.css";
|
||||||
|
import "codemirror/mode/properties/properties";
|
||||||
|
import * as Setting from "./Setting";
|
||||||
|
import IframeEditor from "./IframeEditor";
|
||||||
|
import {Tabs} from "antd";
|
||||||
|
|
||||||
|
const {TabPane} = Tabs;
|
||||||
|
|
||||||
|
const CasbinEditor = ({model, onModelTextChange}) => {
|
||||||
|
const [activeKey, setActiveKey] = useState("advanced");
|
||||||
|
const iframeRef = useRef(null);
|
||||||
|
const [localModelText, setLocalModelText] = useState(model.modelText);
|
||||||
|
|
||||||
|
const handleModelTextChange = useCallback((newModelText) => {
|
||||||
|
if (!Setting.builtInObject(model)) {
|
||||||
|
setLocalModelText(newModelText);
|
||||||
|
onModelTextChange(newModelText);
|
||||||
|
}
|
||||||
|
}, [model, onModelTextChange]);
|
||||||
|
|
||||||
|
const syncModelText = useCallback(() => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (activeKey === "advanced" && iframeRef.current) {
|
||||||
|
const handleSyncMessage = (event) => {
|
||||||
|
if (event.data.type === "modelUpdate") {
|
||||||
|
window.removeEventListener("message", handleSyncMessage);
|
||||||
|
handleModelTextChange(event.data.modelText);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener("message", handleSyncMessage);
|
||||||
|
iframeRef.current.getModelText();
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [activeKey, handleModelTextChange]);
|
||||||
|
|
||||||
|
const handleTabChange = (key) => {
|
||||||
|
syncModelText().then(() => {
|
||||||
|
setActiveKey(key);
|
||||||
|
if (key === "advanced" && iframeRef.current) {
|
||||||
|
iframeRef.current.updateModelText(localModelText);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLocalModelText(model.modelText);
|
||||||
|
}, [model.modelText]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{height: "100%", width: "100%", display: "flex", flexDirection: "column"}}>
|
||||||
|
<Tabs activeKey={activeKey} onChange={handleTabChange} style={{flex: "0 0 auto", marginTop: "-10px"}}>
|
||||||
|
<TabPane tab="Basic Editor" key="basic" />
|
||||||
|
<TabPane tab="Advanced Editor" key="advanced" />
|
||||||
|
</Tabs>
|
||||||
|
<div style={{flex: "1 1 auto", overflow: "hidden"}}>
|
||||||
|
{activeKey === "advanced" ? (
|
||||||
|
<IframeEditor
|
||||||
|
ref={iframeRef}
|
||||||
|
initialModelText={localModelText}
|
||||||
|
onModelTextChange={handleModelTextChange}
|
||||||
|
style={{width: "100%", height: "100%"}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<CodeMirror
|
||||||
|
value={localModelText}
|
||||||
|
className="full-height-editor no-horizontal-scroll-editor"
|
||||||
|
options={{mode: "properties", theme: "default"}}
|
||||||
|
onBeforeChange={(editor, data, value) => {
|
||||||
|
handleModelTextChange(value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CasbinEditor;
|
@ -288,14 +288,14 @@ class CertEditPage extends React.Component {
|
|||||||
Setting.showMessage("success", i18next.t("general:Successfully saved"));
|
Setting.showMessage("success", i18next.t("general:Successfully saved"));
|
||||||
this.setState({
|
this.setState({
|
||||||
certName: this.state.cert.name,
|
certName: this.state.cert.name,
|
||||||
|
}, () => {
|
||||||
|
if (exitAfterSave) {
|
||||||
|
this.props.history.push("/certs");
|
||||||
|
} else {
|
||||||
|
this.props.history.push(`/certs/${this.state.cert.owner}/${this.state.cert.name}`);
|
||||||
|
this.getCert();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (exitAfterSave) {
|
|
||||||
this.props.history.push("/certs");
|
|
||||||
} else {
|
|
||||||
this.props.history.push(`/certs/${this.state.cert.owner}/${this.state.cert.name}`);
|
|
||||||
this.getCert();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
|
||||||
this.updateCertField("name", this.state.certName);
|
this.updateCertField("name", this.state.certName);
|
||||||
|
@ -73,9 +73,11 @@ class CertListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -55,9 +55,11 @@ class EnforcerListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -32,7 +32,9 @@ import {authConfig} from "./auth/Auth";
|
|||||||
import ProductBuyPage from "./ProductBuyPage";
|
import ProductBuyPage from "./ProductBuyPage";
|
||||||
import PaymentResultPage from "./PaymentResultPage";
|
import PaymentResultPage from "./PaymentResultPage";
|
||||||
import QrCodePage from "./QrCodePage";
|
import QrCodePage from "./QrCodePage";
|
||||||
|
import CaptchaPage from "./CaptchaPage";
|
||||||
import CustomHead from "./basic/CustomHead";
|
import CustomHead from "./basic/CustomHead";
|
||||||
|
import * as Util from "./auth/Util";
|
||||||
|
|
||||||
class EntryPage extends React.Component {
|
class EntryPage extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@ -93,10 +95,20 @@ class EntryPage extends React.Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (this.state.application?.ipRestriction) {
|
||||||
|
return Util.renderMessageLarge(this, this.state.application.ipRestriction);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.application?.organizationObj?.ipRestriction) {
|
||||||
|
return Util.renderMessageLarge(this, this.state.application.organizationObj.ipRestriction);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isDarkMode = this.props.themeAlgorithm.includes("dark");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<CustomHead headerHtml={this.state.application?.headerHtml} />
|
<CustomHead headerHtml={this.state.application?.headerHtml} />
|
||||||
<div className="loginBackground"
|
<div className={`${isDarkMode ? "loginBackgroundDark" : "loginBackground"}`}
|
||||||
style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${this.state.application?.formBackgroundUrl})`}}>
|
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")}
|
<Spin size="large" spinning={this.state.application === undefined && this.state.pricing === undefined} tip={i18next.t("login:Loading")}
|
||||||
style={{margin: "0 auto"}} />
|
style={{margin: "0 auto"}} />
|
||||||
@ -120,8 +132,10 @@ class EntryPage extends React.Component {
|
|||||||
<Route exact path="/buy-plan/:owner/:pricingName" render={(props) => <ProductBuyPage {...this.props} pricing={this.state.pricing} onUpdatePricing={onUpdatePricing} {...props} />} />
|
<Route exact path="/buy-plan/:owner/:pricingName" render={(props) => <ProductBuyPage {...this.props} pricing={this.state.pricing} onUpdatePricing={onUpdatePricing} {...props} />} />
|
||||||
<Route exact path="/buy-plan/:owner/:pricingName/result" render={(props) => <PaymentResultPage {...this.props} pricing={this.state.pricing} onUpdatePricing={onUpdatePricing} {...props} />} />
|
<Route exact path="/buy-plan/:owner/:pricingName/result" render={(props) => <PaymentResultPage {...this.props} pricing={this.state.pricing} onUpdatePricing={onUpdatePricing} {...props} />} />
|
||||||
<Route exact path="/qrcode/:owner/:paymentName" render={(props) => <QrCodePage {...this.props} onUpdateApplication={onUpdateApplication} {...props} />} />
|
<Route exact path="/qrcode/:owner/:paymentName" render={(props) => <QrCodePage {...this.props} onUpdateApplication={onUpdateApplication} {...props} />} />
|
||||||
|
<Route exact path="/captcha" render={(props) => <CaptchaPage {...props} />} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -84,9 +84,11 @@ class GroupListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
66
web/src/IframeEditor.js
Normal file
66
web/src/IframeEditor.js
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
// Copyright 2024 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, {forwardRef, useEffect, useImperativeHandle, useRef, useState} from "react";
|
||||||
|
|
||||||
|
const IframeEditor = forwardRef(({initialModelText, onModelTextChange}, ref) => {
|
||||||
|
const iframeRef = useRef(null);
|
||||||
|
const [iframeReady, setIframeReady] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleMessage = (event) => {
|
||||||
|
if (event.origin !== "https://editor.casbin.org") {return;}
|
||||||
|
|
||||||
|
if (event.data.type === "modelUpdate") {
|
||||||
|
onModelTextChange(event.data.modelText);
|
||||||
|
} else if (event.data.type === "iframeReady") {
|
||||||
|
setIframeReady(true);
|
||||||
|
iframeRef.current?.contentWindow.postMessage({
|
||||||
|
type: "initializeModel",
|
||||||
|
modelText: initialModelText,
|
||||||
|
}, "*");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("message", handleMessage);
|
||||||
|
return () => window.removeEventListener("message", handleMessage);
|
||||||
|
}, [onModelTextChange, initialModelText]);
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
getModelText: () => {
|
||||||
|
iframeRef.current?.contentWindow.postMessage({type: "getModelText"}, "*");
|
||||||
|
},
|
||||||
|
updateModelText: (newModelText) => {
|
||||||
|
if (iframeReady) {
|
||||||
|
iframeRef.current?.contentWindow.postMessage({
|
||||||
|
type: "updateModelText",
|
||||||
|
modelText: newModelText,
|
||||||
|
}, "*");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<iframe
|
||||||
|
ref={iframeRef}
|
||||||
|
src="https://editor.casbin.org/model-editor"
|
||||||
|
frameBorder="0"
|
||||||
|
width="100%"
|
||||||
|
height="500px"
|
||||||
|
title="Casbin Model Editor"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default IframeEditor;
|
@ -20,6 +20,7 @@ import * as ApplicationBackend from "./backend/ApplicationBackend";
|
|||||||
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";
|
||||||
|
import * as GroupBackend from "./backend/GroupBackend";
|
||||||
|
|
||||||
const {Option} = Select;
|
const {Option} = Select;
|
||||||
|
|
||||||
@ -33,6 +34,7 @@ class InvitationEditPage extends React.Component {
|
|||||||
invitation: null,
|
invitation: null,
|
||||||
organizations: [],
|
organizations: [],
|
||||||
applications: [],
|
applications: [],
|
||||||
|
groups: [],
|
||||||
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -41,6 +43,7 @@ class InvitationEditPage extends React.Component {
|
|||||||
this.getInvitation();
|
this.getInvitation();
|
||||||
this.getOrganizations();
|
this.getOrganizations();
|
||||||
this.getApplicationsByOrganization(this.state.organizationName);
|
this.getApplicationsByOrganization(this.state.organizationName);
|
||||||
|
this.getGroupsByOrganization(this.state.organizationName);
|
||||||
}
|
}
|
||||||
|
|
||||||
getInvitation() {
|
getInvitation() {
|
||||||
@ -75,6 +78,17 @@ class InvitationEditPage extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getGroupsByOrganization(organizationName) {
|
||||||
|
GroupBackend.getGroups(organizationName)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
this.setState({
|
||||||
|
groups: res.data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
parseInvitationField(key, value) {
|
parseInvitationField(key, value) {
|
||||||
if ([""].includes(key)) {
|
if ([""].includes(key)) {
|
||||||
value = Setting.myParseInt(value);
|
value = Setting.myParseInt(value);
|
||||||
@ -120,7 +134,7 @@ class InvitationEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Select virtual={false} style={{width: "100%"}} disabled={!Setting.isAdminUser(this.props.account) || isCreatedByPlan} value={this.state.invitation.owner} onChange={(value => {this.updateInvitationField("owner", value); this.getApplicationsByOrganization(value);})}>
|
<Select virtual={false} style={{width: "100%"}} disabled={!Setting.isAdminUser(this.props.account) || isCreatedByPlan} value={this.state.invitation.owner} onChange={(value => {this.updateInvitationField("owner", value); this.getApplicationsByOrganization(value);this.getGroupsByOrganization(value);})}>
|
||||||
{
|
{
|
||||||
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
|
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
|
||||||
}
|
}
|
||||||
@ -204,6 +218,21 @@ class InvitationEditPage extends React.Component {
|
|||||||
]} />
|
]} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("provider:Signup group"), i18next.t("provider:Signup group - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: "100%"}} value={this.state.invitation.signupGroup} onChange={(value => {this.updateInvitationField("signupGroup", value);})}>
|
||||||
|
<Option key={""} value={""}>
|
||||||
|
{i18next.t("general:Default")}
|
||||||
|
</Option>
|
||||||
|
{
|
||||||
|
this.state.groups.map((group, index) => <Option key={index} value={`${group.owner}/${group.name}`}>{group.name}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("signup:Username"), i18next.t("signup:Username - Tooltip"))} :
|
{Setting.getLabel(i18next.t("signup:Username"), i18next.t("signup:Username - Tooltip"))} :
|
||||||
|
@ -68,9 +68,11 @@ class InvitationListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -13,12 +13,13 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Button, Card, Col, Input, InputNumber, Row, Select, Switch} from "antd";
|
import {Button, Card, Col, Input, InputNumber, Row, Select, Space, Switch} from "antd";
|
||||||
import {EyeInvisibleOutlined, EyeTwoTone} from "@ant-design/icons";
|
import {EyeInvisibleOutlined, EyeTwoTone, HolderOutlined, UsergroupAddOutlined} from "@ant-design/icons";
|
||||||
import * as LddpBackend from "./backend/LdapBackend";
|
import * as LddpBackend from "./backend/LdapBackend";
|
||||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
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 * as GroupBackend from "./backend/GroupBackend";
|
||||||
|
|
||||||
const {Option} = Select;
|
const {Option} = Select;
|
||||||
|
|
||||||
@ -30,12 +31,14 @@ class LdapEditPage extends React.Component {
|
|||||||
organizationName: props.match.params.organizationName,
|
organizationName: props.match.params.organizationName,
|
||||||
ldap: null,
|
ldap: null,
|
||||||
organizations: [],
|
organizations: [],
|
||||||
|
groups: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
UNSAFE_componentWillMount() {
|
UNSAFE_componentWillMount() {
|
||||||
this.getLdap();
|
this.getLdap();
|
||||||
this.getOrganizations();
|
this.getOrganizations();
|
||||||
|
this.getGroups();
|
||||||
}
|
}
|
||||||
|
|
||||||
getLdap() {
|
getLdap() {
|
||||||
@ -60,6 +63,17 @@ class LdapEditPage extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getGroups() {
|
||||||
|
GroupBackend.getGroups(this.state.organizationName)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
this.setState({
|
||||||
|
groups: res.data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
updateLdapField(key, value) {
|
updateLdapField(key, value) {
|
||||||
this.setState((prevState) => {
|
this.setState((prevState) => {
|
||||||
prevState.ldap[key] = value;
|
prevState.ldap[key] = value;
|
||||||
@ -214,6 +228,31 @@ class LdapEditPage extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{lineHeight: "32px", textAlign: "right", paddingRight: "25px"}} span={3}>
|
||||||
|
{Setting.getLabel(i18next.t("ldap:Default group"), i18next.t("ldap:Default group - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={21}>
|
||||||
|
<Select virtual={false} style={{width: "100%"}} value={this.state.ldap.defaultGroup ?? []} onChange={(value => {
|
||||||
|
this.updateLdapField("defaultGroup", value);
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Option key={""} value={""}>
|
||||||
|
<Space>
|
||||||
|
{i18next.t("general:Default")}
|
||||||
|
</Space>
|
||||||
|
</Option>
|
||||||
|
{
|
||||||
|
this.state.groups?.map((group) => <Option key={group.name} value={`${group.owner}/${group.name}`}>
|
||||||
|
<Space>
|
||||||
|
{group.type === "Physical" ? <UsergroupAddOutlined /> : <HolderOutlined />}
|
||||||
|
{group.displayName}
|
||||||
|
</Space>
|
||||||
|
</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
<Row style={{marginTop: "20px"}}>
|
<Row style={{marginTop: "20px"}}>
|
||||||
<Col style={{lineHeight: "32px", textAlign: "right", paddingRight: "25px"}} span={3}>
|
<Col style={{lineHeight: "32px", textAlign: "right", paddingRight: "25px"}} span={3}>
|
||||||
{Setting.getLabel(i18next.t("ldap:Auto Sync"), i18next.t("ldap:Auto Sync - Tooltip"))} :
|
{Setting.getLabel(i18next.t("ldap:Auto Sync"), i18next.t("ldap:Auto Sync - Tooltip"))} :
|
||||||
|
@ -198,11 +198,11 @@ function ManagementPage(props) {
|
|||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<OpenTour />
|
<OpenTour />
|
||||||
{Setting.isAdminUser(props.account) && !Setting.isMobile() && (props.uri.indexOf("/trees") === -1) &&
|
{Setting.isAdminUser(props.account) && (props.uri.indexOf("/trees") === -1) &&
|
||||||
<OrganizationSelect
|
<OrganizationSelect
|
||||||
initValue={Setting.getOrganization()}
|
initValue={Setting.getOrganization()}
|
||||||
withAll={true}
|
withAll={true}
|
||||||
style={{marginRight: "20px", width: "180px", display: "flex"}}
|
style={{marginRight: "20px", width: "180px", display: !Setting.isMobile() ? "flex" : "none"}}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
Setting.setOrganization(value);
|
Setting.setOrganization(value);
|
||||||
}}
|
}}
|
||||||
|
@ -18,11 +18,7 @@ import * as ModelBackend from "./backend/ModelBackend";
|
|||||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
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 ModelEditor from "./CasbinEditor";
|
||||||
import {Controlled as CodeMirror} from "react-codemirror2";
|
|
||||||
import "codemirror/lib/codemirror.css";
|
|
||||||
|
|
||||||
require("codemirror/mode/properties/properties");
|
|
||||||
|
|
||||||
const {Option} = Select;
|
const {Option} = Select;
|
||||||
|
|
||||||
@ -147,16 +143,10 @@ class ModelEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("model:Model text"), i18next.t("model:Model text - Tooltip"))} :
|
{Setting.getLabel(i18next.t("model:Model text"), i18next.t("model:Model text - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22}>
|
<Col span={22}>
|
||||||
<div style={{width: "100%"}} >
|
<div style={{position: "relative", height: "500px"}} >
|
||||||
<CodeMirror
|
<ModelEditor
|
||||||
value={this.state.model.modelText}
|
model={this.state.model}
|
||||||
options={{mode: "properties", theme: "default"}}
|
onModelTextChange={(value) => this.updateModelField("modelText", value)}
|
||||||
onBeforeChange={(editor, data, value) => {
|
|
||||||
if (Setting.builtInObject(this.state.model)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.updateModelField("modelText", value);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -72,9 +72,11 @@ class ModelListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -19,6 +19,7 @@ import * as ApplicationBackend from "./backend/ApplicationBackend";
|
|||||||
import * as LdapBackend from "./backend/LdapBackend";
|
import * as LdapBackend from "./backend/LdapBackend";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import * as Conf from "./Conf";
|
import * as Conf from "./Conf";
|
||||||
|
import * as Obfuscator from "./auth/Obfuscator";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import {LinkOutlined} from "@ant-design/icons";
|
import {LinkOutlined} from "@ant-design/icons";
|
||||||
import LdapTable from "./table/LdapTable";
|
import LdapTable from "./table/LdapTable";
|
||||||
@ -112,6 +113,22 @@ class OrganizationEditPage extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updatePasswordObfuscator(key, value) {
|
||||||
|
const organization = this.state.organization;
|
||||||
|
if (organization.passwordObfuscatorType === "") {
|
||||||
|
organization.passwordObfuscatorType = "Plain";
|
||||||
|
}
|
||||||
|
if (key === "type") {
|
||||||
|
organization.passwordObfuscatorType = value;
|
||||||
|
organization.passwordObfuscatorKey = Obfuscator.getRandomKeyForObfuscator(value);
|
||||||
|
} else if (key === "key") {
|
||||||
|
organization.passwordObfuscatorKey = value;
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
organization: organization,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
renderOrganization() {
|
renderOrganization() {
|
||||||
return (
|
return (
|
||||||
<Card size="small" title={
|
<Card size="small" title={
|
||||||
@ -294,6 +311,44 @@ 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:Password obfuscator"), i18next.t("general:Password obfuscator - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: "100%"}}
|
||||||
|
value={this.state.organization.passwordObfuscatorType}
|
||||||
|
onChange={(value => {this.updatePasswordObfuscator("type", value);})}>
|
||||||
|
{
|
||||||
|
[
|
||||||
|
{id: "Plain", name: "Plain"},
|
||||||
|
{id: "AES", name: "AES"},
|
||||||
|
{id: "DES", name: "DES"},
|
||||||
|
].map((obfuscatorType, index) => <Option key={index} value={obfuscatorType.id}>{obfuscatorType.name}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
{
|
||||||
|
(this.state.organization.passwordObfuscatorType === "Plain" || this.state.organization.passwordObfuscatorType === "") ? null : (<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Password obf key"), i18next.t("general:Password obf key - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.organization.passwordObfuscatorKey} onChange={(e) => {this.updatePasswordObfuscator("key", e.target.value);}} />
|
||||||
|
</Col>
|
||||||
|
</Row>)
|
||||||
|
}
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("organization:Password expire days"), i18next.t("organization:Password expire days - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={4} >
|
||||||
|
<InputNumber value={this.state.organization.passwordExpireDays} onChange={value => {
|
||||||
|
this.updateOrganizationField("passwordExpireDays", 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("general:Supported country codes"), i18next.t("general:Supported country codes - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Supported country codes"), i18next.t("general:Supported country codes - Tooltip"))} :
|
||||||
@ -305,6 +360,7 @@ class OrganizationEditPage extends React.Component {
|
|||||||
}}
|
}}
|
||||||
filterOption={(input, option) => (option?.text ?? "").toLowerCase().includes(input.toLowerCase())}
|
filterOption={(input, option) => (option?.text ?? "").toLowerCase().includes(input.toLowerCase())}
|
||||||
>
|
>
|
||||||
|
{Setting.getCountryCodeOption({name: i18next.t("organization:All"), code: "All", phone: 0})}
|
||||||
{
|
{
|
||||||
Setting.getCountryCodeData().map((country) => Setting.getCountryCodeOption(country))
|
Setting.getCountryCodeData().map((country) => Setting.getCountryCodeOption(country))
|
||||||
}
|
}
|
||||||
@ -360,7 +416,7 @@ class OrganizationEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Select virtual={false} style={{width: "100%"}} value={this.state.organization.defaultApplication} onChange={(value => {this.updateOrganizationField("defaultApplication", value);})}
|
<Select virtual={false} style={{width: "100%"}} value={this.state.organization.defaultApplication} onChange={(value => {this.updateOrganizationField("defaultApplication", value);})}
|
||||||
options={this.state.applications?.map((item) => Setting.getOption(item.name, item.name))
|
options={this.state.applications?.map((item) => Setting.getOption(Setting.getApplicationDisplayName(item.name), item.name))
|
||||||
} />
|
} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
@ -406,6 +462,16 @@ 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:IP whitelist"), i18next.t("general:IP whitelist - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.organization.ipWhitelist} onChange={e => {
|
||||||
|
this.updateOrganizationField("ipWhitelist", e.target.value);
|
||||||
|
}} />
|
||||||
|
</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"))} :
|
||||||
@ -528,6 +594,12 @@ class OrganizationEditPage extends React.Component {
|
|||||||
const organization = Setting.deepCopy(this.state.organization);
|
const organization = Setting.deepCopy(this.state.organization);
|
||||||
organization.accountItems = organization.accountItems?.filter(accountItem => accountItem.name !== "Please select an account item");
|
organization.accountItems = organization.accountItems?.filter(accountItem => accountItem.name !== "Please select an account item");
|
||||||
|
|
||||||
|
const passwordObfuscatorErrorMessage = Obfuscator.checkPasswordObfuscator(organization.passwordObfuscatorType, organization.passwordObfuscatorKey);
|
||||||
|
if (passwordObfuscatorErrorMessage.length > 0) {
|
||||||
|
Setting.showMessage("error", passwordObfuscatorErrorMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
OrganizationBackend.updateOrganization(this.state.organization.owner, this.state.organizationName, organization)
|
OrganizationBackend.updateOrganization(this.state.organization.owner, this.state.organizationName, organization)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
|
@ -35,6 +35,9 @@ class OrganizationListPage extends BaseListPage {
|
|||||||
passwordType: "plain",
|
passwordType: "plain",
|
||||||
PasswordSalt: "",
|
PasswordSalt: "",
|
||||||
passwordOptions: [],
|
passwordOptions: [],
|
||||||
|
passwordObfuscatorType: "Plain",
|
||||||
|
passwordObfuscatorKey: "",
|
||||||
|
passwordExpireDays: 0,
|
||||||
countryCodes: ["US"],
|
countryCodes: ["US"],
|
||||||
defaultAvatar: `${Setting.StaticBaseUrl}/img/casbin.svg`,
|
defaultAvatar: `${Setting.StaticBaseUrl}/img/casbin.svg`,
|
||||||
defaultApplication: "",
|
defaultApplication: "",
|
||||||
@ -115,11 +118,11 @@ class OrganizationListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
|
||||||
pagination: {
|
pagination: {
|
||||||
...this.state.pagination,
|
...this.state.pagination,
|
||||||
total: this.state.pagination.total - 1},
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
window.dispatchEvent(new Event("storageOrganizationsChanged"));
|
window.dispatchEvent(new Event("storageOrganizationsChanged"));
|
||||||
} else {
|
} else {
|
||||||
|
@ -70,9 +70,11 @@ class PaymentListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -69,9 +69,11 @@ class PermissionListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -63,9 +63,11 @@ class PlanListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -59,9 +59,11 @@ class PricingListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -123,6 +123,22 @@ class ProductBuyPage extends React.Component {
|
|||||||
return "$";
|
return "$";
|
||||||
} else if (product?.currency === "CNY") {
|
} else if (product?.currency === "CNY") {
|
||||||
return "¥";
|
return "¥";
|
||||||
|
} else if (product?.currency === "EUR") {
|
||||||
|
return "€";
|
||||||
|
} else if (product?.currency === "JPY") {
|
||||||
|
return "¥";
|
||||||
|
} else if (product?.currency === "GBP") {
|
||||||
|
return "£";
|
||||||
|
} else if (product?.currency === "AUD") {
|
||||||
|
return "A$";
|
||||||
|
} else if (product?.currency === "CAD") {
|
||||||
|
return "C$";
|
||||||
|
} else if (product?.currency === "CHF") {
|
||||||
|
return "CHF";
|
||||||
|
} else if (product?.currency === "HKD") {
|
||||||
|
return "HK$";
|
||||||
|
} else if (product?.currency === "SGD") {
|
||||||
|
return "S$";
|
||||||
} else {
|
} else {
|
||||||
return "(Unknown currency)";
|
return "(Unknown currency)";
|
||||||
}
|
}
|
||||||
|
@ -209,6 +209,14 @@ class ProductEditPage extends React.Component {
|
|||||||
[
|
[
|
||||||
{id: "USD", name: "USD"},
|
{id: "USD", name: "USD"},
|
||||||
{id: "CNY", name: "CNY"},
|
{id: "CNY", name: "CNY"},
|
||||||
|
{id: "EUR", name: "EUR"},
|
||||||
|
{id: "JPY", name: "JPY"},
|
||||||
|
{id: "GBP", name: "GBP"},
|
||||||
|
{id: "AUD", name: "AUD"},
|
||||||
|
{id: "CAD", name: "CAD"},
|
||||||
|
{id: "CHF", name: "CHF"},
|
||||||
|
{id: "HKD", name: "HKD"},
|
||||||
|
{id: "SGD", name: "SGD"},
|
||||||
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
||||||
}
|
}
|
||||||
</Select>
|
</Select>
|
||||||
|
@ -65,9 +65,11 @@ class ProductListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -843,7 +843,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
this.state.provider.type !== "ADFS" && this.state.provider.type !== "AzureAD" && this.state.provider.type !== "AzureADB2C" && this.state.provider.type !== "Casdoor" && this.state.provider.type !== "Okta" ? null : (
|
this.state.provider.type !== "ADFS" && this.state.provider.type !== "AzureAD" && this.state.provider.type !== "AzureADB2C" && (this.state.provider.type !== "Casdoor" && this.state.category !== "Storage") && this.state.provider.type !== "Okta" ? null : (
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={2}>
|
<Col style={{marginTop: "5px"}} span={2}>
|
||||||
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
|
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
|
||||||
@ -870,7 +870,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
)}
|
)}
|
||||||
{["Custom HTTP SMS", "Local File System", "MinIO", "Tencent Cloud COS", "Google Cloud Storage", "Qiniu Cloud Kodo", "Synology"].includes(this.state.provider.type) ? null : (
|
{["Custom HTTP SMS", "Local File System", "MinIO", "Tencent Cloud COS", "Google Cloud Storage", "Qiniu Cloud Kodo", "Synology", "Casdoor"].includes(this.state.provider.type) ? null : (
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={2}>
|
<Col style={{marginTop: "5px"}} span={2}>
|
||||||
{Setting.getLabel(i18next.t("provider:Endpoint (Intranet)"), i18next.t("provider:Region endpoint for Intranet"))} :
|
{Setting.getLabel(i18next.t("provider:Endpoint (Intranet)"), i18next.t("provider:Region endpoint for Intranet"))} :
|
||||||
@ -885,7 +885,9 @@ class ProviderEditPage extends React.Component {
|
|||||||
{["Custom HTTP SMS", "Local File System"].includes(this.state.provider.type) ? null : (
|
{["Custom HTTP SMS", "Local File System"].includes(this.state.provider.type) ? null : (
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={2}>
|
<Col style={{marginTop: "5px"}} span={2}>
|
||||||
{Setting.getLabel(i18next.t("provider:Bucket"), i18next.t("provider:Bucket - Tooltip"))} :
|
{["Casdoor"].includes(this.state.provider.type) ?
|
||||||
|
Setting.getLabel(i18next.t("general:Provider"), i18next.t("provider:Provider - Tooltip"))
|
||||||
|
: Setting.getLabel(i18next.t("provider:Bucket"), i18next.t("provider:Bucket - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Input value={this.state.provider.bucket} onChange={e => {
|
<Input value={this.state.provider.bucket} onChange={e => {
|
||||||
@ -906,7 +908,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
)}
|
)}
|
||||||
{["Custom HTTP SMS", "Google Cloud Storage", "Qiniu Cloud Kodo", "Synology"].includes(this.state.provider.type) ? null : (
|
{["Custom HTTP SMS", "Synology", "Casdoor"].includes(this.state.provider.type) ? null : (
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={2}>
|
<Col style={{marginTop: "5px"}} span={2}>
|
||||||
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
|
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
|
||||||
@ -918,10 +920,24 @@ class ProviderEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
)}
|
)}
|
||||||
{["AWS S3", "Tencent Cloud COS", "Qiniu Cloud Kodo"].includes(this.state.provider.type) ? (
|
{["Casdoor"].includes(this.state.provider.type) ? (
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={2}>
|
<Col style={{marginTop: "5px"}} span={2}>
|
||||||
{Setting.getLabel(i18next.t("provider:Region ID"), i18next.t("provider:Region ID - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.provider.content} onChange={e => {
|
||||||
|
this.updateProviderField("content", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
) : null}
|
||||||
|
{["AWS S3", "Tencent Cloud COS", "Qiniu Cloud Kodo", "Casdoor", "CUCloud OSS"].includes(this.state.provider.type) ? (
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={2}>
|
||||||
|
{["Casdoor"].includes(this.state.provider.type) ?
|
||||||
|
Setting.getLabel(i18next.t("general:Application"), i18next.t("general:Application - Tooltip")) :
|
||||||
|
Setting.getLabel(i18next.t("provider:Region ID"), i18next.t("provider:Region ID - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Input value={this.state.provider.regionId} onChange={e => {
|
<Input value={this.state.provider.regionId} onChange={e => {
|
||||||
@ -1298,7 +1314,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
(this.state.provider.type === "Alipay" || this.state.provider.type === "WeChat Pay") ? (
|
(this.state.provider.type === "Alipay" || this.state.provider.type === "WeChat Pay" || this.state.provider.type === "Casdoor") ? (
|
||||||
<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:Cert"), i18next.t("general:Cert - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Cert"), i18next.t("general:Cert - Tooltip"))} :
|
||||||
|
@ -76,9 +76,11 @@ class ProviderListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -40,9 +40,11 @@ class ResourceListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -187,7 +187,7 @@ class RoleEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("role:Sub users"), i18next.t("role:Sub users - Tooltip"))} :
|
{Setting.getLabel(i18next.t("role:Sub users"), i18next.t("role:Sub users - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Select virtual={false} mode="multiple" style={{width: "100%"}} value={this.state.role.users}
|
<Select virtual={true} mode="multiple" style={{width: "100%"}} value={this.state.role.users}
|
||||||
onChange={(value => {this.updateRoleField("users", value);})}
|
onChange={(value => {this.updateRoleField("users", value);})}
|
||||||
options={this.state.users.map((user) => Setting.getOption(`${user.owner}/${user.name}`, `${user.owner}/${user.name}`))}
|
options={this.state.users.map((user) => Setting.getOption(`${user.owner}/${user.name}`, `${user.owner}/${user.name}`))}
|
||||||
/>
|
/>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user