Compare commits

..

1 Commits

Author SHA1 Message Date
7e46222ecd Revert "feat: add Casbin editor's checking in model editor (#3166)"
This reverts commit a1b010a406.
2024-09-03 21:58:51 +08:00
141 changed files with 2050 additions and 5614 deletions

View File

@ -114,12 +114,12 @@ jobs:
wait-on-timeout: 210
working-directory: ./web
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
if: failure()
with:
name: cypress-screenshots
path: ./web/cypress/screenshots
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
if: always()
with:
name: cypress-videos
@ -147,7 +147,7 @@ jobs:
- name: Release
run: yarn global add semantic-release@17.4.4 && semantic-release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }}
- name: Fetch Current version
id: get-current-tag

View File

@ -13,7 +13,7 @@
<a href="https://github.com/casdoor/casdoor/releases/latest">
<img alt="GitHub Release" src="https://img.shields.io/github/v/release/casdoor/casdoor.svg">
</a>
<a href="https://hub.docker.com/r/casbin/casdoor">
<a href="https://hub.docker.com/repository/docker/casbin/casdoor">
<img alt="Docker Image Version (latest semver)" src="https://img.shields.io/badge/Docker%20Hub-latest-brightgreen">
</a>
</p>

View File

@ -77,7 +77,6 @@ p, *, *, POST, /api/verify-code, *, *
p, *, *, POST, /api/reset-email-or-phone, *, *
p, *, *, POST, /api/upload-resource, *, *
p, *, *, GET, /.well-known/openid-configuration, *, *
p, *, *, GET, /.well-known/webfinger, *, *
p, *, *, *, /.well-known/jwks, *, *
p, *, *, GET, /api/get-saml-login, *, *
p, *, *, POST, /api/acs, *, *
@ -98,7 +97,6 @@ p, *, *, GET, /api/get-organization-names, *, *
p, *, *, GET, /api/get-all-objects, *, *
p, *, *, GET, /api/get-all-actions, *, *
p, *, *, GET, /api/get-all-roles, *, *
p, *, *, GET, /api/run-casbin-command, *, *
p, *, *, GET, /api/get-invitation-info, *, *
p, *, *, GET, /api/faceid-signin-begin, *, *
`

View File

@ -23,15 +23,10 @@ isDemoMode = false
batchSize = 100
enableErrorMask = false
enableGzip = true
inactiveTimeoutMinutes =
ldapServerPort = 389
ldapsCertId = ""
ldapsServerPort = 636
radiusServerPort = 1812
radiusDefaultOrganization = "built-in"
radiusSecret = "secret"
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}
logConfig = {"filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"}
initDataNewOnly = false
initDataFile = "./init_data.json"
frontendBaseDir = "../cc_0"
frontendBaseDir = "../casdoor"

View File

@ -116,13 +116,6 @@ func (c *ApiController) Signup() {
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())
if msg != "" {
c.ResponseError(msg)
@ -207,10 +200,6 @@ func (c *ApiController) Signup() {
Type: userType,
Password: authForm.Password,
DisplayName: authForm.Name,
Gender: authForm.Gender,
Bio: authForm.Bio,
Tag: authForm.Tag,
Education: authForm.Education,
Avatar: organization.DefaultAvatar,
Email: authForm.Email,
Phone: authForm.Phone,
@ -245,10 +234,6 @@ func (c *ApiController) Signup() {
}
}
if invitation != nil && invitation.SignupGroup != "" {
user.Groups = []string{invitation.SignupGroup}
}
affected, err := object.AddUser(user)
if err != nil {
c.ResponseError(err.Error())

View File

@ -110,9 +110,6 @@ func (c *ApiController) GetApplication() {
}
}
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
object.CheckEntryIp(clientIp, nil, application, nil, c.GetAcceptLanguage())
c.ResponseOk(object.GetMaskedApplication(application, userId))
}
@ -232,11 +229,6 @@ func (c *ApiController) UpdateApplication() {
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.ServeJSON()
}
@ -267,11 +259,6 @@ func (c *ApiController) AddApplication() {
return
}
if err = object.CheckIpWhitelist(application.IpWhitelist, c.GetAcceptLanguage()); err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.AddApplication(&application))
c.ServeJSON()
}

View File

@ -22,7 +22,6 @@ import (
"io"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
@ -56,13 +55,6 @@ func tokenToResponse(token *object.Token) *Response {
func (c *ApiController) HandleLoggedIn(application *object.Application, user *object.User, form *form.AuthForm) (resp *Response) {
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)
if err != nil {
c.ResponseError(err.Error(), nil)
@ -264,9 +256,6 @@ func (c *ApiController) GetApplicationLogin() {
}
}
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
object.CheckEntryIp(clientIp, nil, application, nil, c.GetAcceptLanguage())
application = object.GetMaskedApplication(application, "")
if msg != "" {
c.ResponseError(msg, application)
@ -474,15 +463,6 @@ func (c *ApiController) Login() {
}
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"
var isPasswordWithLdapEnabled bool
if authForm.SigninMethod == "Password" {
@ -618,17 +598,6 @@ func (c *ApiController) Login() {
c.ResponseError(fmt.Sprintf(c.T("auth:Failed to login in: %s"), err.Error()))
return
}
if provider.EmailRegex != "" {
reg, err := regexp.Compile(provider.EmailRegex)
if err != nil {
c.ResponseError(fmt.Sprintf(c.T("auth:Failed to login in: %s"), err.Error()))
return
}
if !reg.MatchString(userInfo.Email) {
c.ResponseError(fmt.Sprintf(c.T("check:Email is invalid")))
}
}
}
if authForm.Method == "signup" {
@ -866,7 +835,6 @@ func (c *ApiController) Login() {
}
if authForm.Passcode != "" {
user.CountryCode = user.GetCountryCode(user.CountryCode)
mfaUtil := object.GetMfaUtil(authForm.MfaType, user.GetPreferredMfaProps(false))
if mfaUtil == nil {
c.ResponseError("Invalid multi-factor authentication type")

View File

@ -1,114 +0,0 @@
// 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)
}

View File

@ -22,6 +22,13 @@ import (
"github.com/google/uuid"
)
const (
MfaRecoveryCodesSession = "mfa_recovery_codes"
MfaCountryCodeSession = "mfa_country_code"
MfaDestSession = "mfa_dest"
MfaTotpSecretSession = "mfa_totp_secret"
)
// MfaSetupInitiate
// @Title MfaSetupInitiate
// @Tag MFA API
@ -65,6 +72,11 @@ func (c *ApiController) MfaSetupInitiate() {
}
recoveryCode := uuid.NewString()
c.SetSession(MfaRecoveryCodesSession, recoveryCode)
if mfaType == object.TotpType {
c.SetSession(MfaTotpSecretSession, mfaProps.Secret)
}
mfaProps.RecoveryCodes = []string{recoveryCode}
resp := mfaProps
@ -82,9 +94,6 @@ func (c *ApiController) MfaSetupInitiate() {
func (c *ApiController) MfaSetupVerify() {
mfaType := c.Ctx.Request.Form.Get("mfaType")
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 == "" {
c.ResponseError("missing auth type or passcode")
@ -95,28 +104,32 @@ func (c *ApiController) MfaSetupVerify() {
MfaType: mfaType,
}
if mfaType == object.TotpType {
if secret == "" {
secret := c.GetSession(MfaTotpSecretSession)
if secret == nil {
c.ResponseError("totp secret is missing")
return
}
config.Secret = secret
config.Secret = secret.(string)
} else if mfaType == object.SmsType {
if dest == "" {
dest := c.GetSession(MfaDestSession)
if dest == nil {
c.ResponseError("destination is missing")
return
}
config.Secret = dest
if countryCode == "" {
config.Secret = dest.(string)
countryCode := c.GetSession(MfaCountryCodeSession)
if countryCode == nil {
c.ResponseError("country code is missing")
return
}
config.CountryCode = countryCode
config.CountryCode = countryCode.(string)
} else if mfaType == object.EmailType {
if dest == "" {
dest := c.GetSession(MfaDestSession)
if dest == nil {
c.ResponseError("destination is missing")
return
}
config.Secret = dest
config.Secret = dest.(string)
}
mfaUtil := object.GetMfaUtil(mfaType, config)
@ -146,10 +159,6 @@ func (c *ApiController) MfaSetupEnable() {
owner := c.Ctx.Request.Form.Get("owner")
name := c.Ctx.Request.Form.Get("name")
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))
if err != nil {
@ -167,39 +176,43 @@ func (c *ApiController) MfaSetupEnable() {
}
if mfaType == object.TotpType {
if secret == "" {
secret := c.GetSession(MfaTotpSecretSession)
if secret == nil {
c.ResponseError("totp secret is missing")
return
}
config.Secret = secret
config.Secret = secret.(string)
} else if mfaType == object.EmailType {
if user.Email == "" {
if dest == "" {
dest := c.GetSession(MfaDestSession)
if dest == nil {
c.ResponseError("destination is missing")
return
}
user.Email = dest
user.Email = dest.(string)
}
} else if mfaType == object.SmsType {
if user.Phone == "" {
if dest == "" {
dest := c.GetSession(MfaDestSession)
if dest == nil {
c.ResponseError("destination is missing")
return
}
user.Phone = dest
if countryCode == "" {
user.Phone = dest.(string)
countryCode := c.GetSession(MfaCountryCodeSession)
if countryCode == nil {
c.ResponseError("country code is missing")
return
}
user.CountryCode = countryCode
user.CountryCode = countryCode.(string)
}
}
if recoveryCodes == "" {
recoveryCodes := c.GetSession(MfaRecoveryCodesSession)
if recoveryCodes == nil {
c.ResponseError("recovery codes is missing")
return
}
config.RecoveryCodes = []string{recoveryCodes}
config.RecoveryCodes = []string{recoveryCodes.(string)}
mfaUtil := object.GetMfaUtil(mfaType, config)
if mfaUtil == nil {
@ -213,6 +226,14 @@ func (c *ApiController) MfaSetupEnable() {
return
}
c.DelSession(MfaRecoveryCodesSession)
if mfaType == object.TotpType {
c.DelSession(MfaTotpSecretSession)
} else {
c.DelSession(MfaCountryCodeSession)
c.DelSession(MfaDestSession)
}
c.ResponseOk(http.StatusText(http.StatusOK))
}

View File

@ -14,11 +14,7 @@
package controllers
import (
"strings"
"github.com/casdoor/casdoor/object"
)
import "github.com/casdoor/casdoor/object"
// GetOidcDiscovery
// @Title GetOidcDiscovery
@ -46,31 +42,3 @@ func (c *RootController) GetJwks() {
c.Data["json"] = jwks
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()
}

View File

@ -65,7 +65,7 @@ func (c *ApiController) GetOrganizations() {
c.ResponseOk(organizations)
} else {
limit := util.ParseInt(limit)
count, err := object.GetOrganizationCount(owner, organizationName, field, value)
count, err := object.GetOrganizationCount(owner, field, value)
if err != nil {
c.ResponseError(err.Error())
return
@ -119,11 +119,6 @@ func (c *ApiController) UpdateOrganization() {
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.ServeJSON()
}
@ -143,7 +138,7 @@ func (c *ApiController) AddOrganization() {
return
}
count, err := object.GetOrganizationCount("", "", "", "")
count, err := object.GetOrganizationCount("", "", "")
if err != nil {
c.ResponseError(err.Error())
return
@ -154,11 +149,6 @@ func (c *ApiController) AddOrganization() {
return
}
if err = object.CheckIpWhitelist(organization.IpWhitelist, c.GetAcceptLanguage()); err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.AddOrganization(&organization))
c.ServeJSON()
}

View File

@ -182,10 +182,6 @@ func (c *ApiController) BuyProduct() {
paidUserName := c.Input().Get("userName")
owner, _ := util.GetOwnerAndNameFromId(id)
userId := util.GetId(owner, paidUserName)
if paidUserName != "" && !c.IsAdmin() {
c.ResponseError(c.T("general:Only admin user can specify user"))
return
}
if paidUserName == "" {
userId = c.GetSessionUsername()
}

View File

@ -93,7 +93,7 @@ func (c *ApiController) SendEmail() {
// when receiver is the reserved keyword: "TestSmtpServer", it means to test the SMTP server instead of sending a real Email
if len(emailForm.Receivers) == 1 && emailForm.Receivers[0] == "TestSmtpServer" {
err = object.TestSmtpServer(provider)
err = object.DailSmtpServer(provider)
if err != nil {
c.ResponseError(err.Error())
return

View File

@ -322,21 +322,16 @@ func (c *ApiController) IntrospectToken() {
}
tokenTypeHint := c.Input().Get("token_type_hint")
var token *object.Token
if tokenTypeHint != "" {
token, err = object.GetTokenByTokenValue(tokenValue, tokenTypeHint)
if err != nil {
c.ResponseTokenError(err.Error())
return
}
if token == nil {
c.Data["json"] = &object.IntrospectionResponse{Active: false}
c.ServeJSON()
return
}
token, err := object.GetTokenByTokenValue(tokenValue, tokenTypeHint)
if err != nil {
c.ResponseTokenError(err.Error())
return
}
if token == nil {
c.Data["json"] = &object.IntrospectionResponse{Active: false}
c.ServeJSON()
return
}
var introspectionResponse object.IntrospectionResponse
if application.TokenFormat == "JWT-Standard" {
jwtToken, err := object.ParseStandardJwtTokenByApplication(tokenValue, application)
@ -349,37 +344,12 @@ func (c *ApiController) IntrospectToken() {
return
}
introspectionResponse = object.IntrospectionResponse{
c.Data["json"] = &object.IntrospectionResponse{
Active: true,
Scope: jwtToken.Scope,
ClientId: clientId,
Username: jwtToken.Name,
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,
Username: token.User,
TokenType: token.TokenType,
Exp: jwtToken.ExpiresAt.Unix(),
Iat: jwtToken.IssuedAt.Unix(),
Nbf: jwtToken.NotBefore.Unix(),
@ -388,22 +358,33 @@ func (c *ApiController) IntrospectToken() {
Iss: jwtToken.Issuer,
Jti: jwtToken.ID,
}
c.ServeJSON()
return
}
if tokenTypeHint == "" {
token, err = object.GetTokenByTokenValue(tokenValue, introspectionResponse.TokenType)
if err != nil {
c.ResponseTokenError(err.Error())
return
}
if token == nil {
c.Data["json"] = &object.IntrospectionResponse{Active: false}
c.ServeJSON()
return
}
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.TokenType = token.TokenType
c.Data["json"] = introspectionResponse
c.Data["json"] = &object.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()
}

View File

@ -353,13 +353,18 @@ func (c *ApiController) AddUser() {
return
}
if err := checkQuotaForUser(); err != nil {
count, err := object.GetUserCount("", "", "", "")
if err != nil {
c.ResponseError(err.Error())
return
}
emptyUser := object.User{}
msg := object.CheckUpdateUser(&emptyUser, &user, c.GetAcceptLanguage())
if err := checkQuotaForUser(int(count)); err != nil {
c.ResponseError(err.Error())
return
}
msg := object.CheckUsername(user.Name, c.GetAcceptLanguage())
if msg != "" {
c.ResponseError(msg)
return
@ -405,12 +410,6 @@ func (c *ApiController) GetEmailAndPhone() {
organization := c.Ctx.Request.Form.Get("organization")
username := c.Ctx.Request.Form.Get("username")
enableErrorMask2 := conf.GetConfigBool("enableErrorMask2")
if enableErrorMask2 {
c.ResponseError("Error")
return
}
user, err := object.GetUserByFields(organization, username)
if err != nil {
c.ResponseError(err.Error())
@ -469,16 +468,6 @@ func (c *ApiController) SetPassword() {
userId := util.GetId(userOwner, userName)
user, err := object.GetUser(userId)
if err != nil {
c.ResponseError(err.Error())
return
}
if user == nil {
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), userId))
return
}
requestUserId := c.GetSessionUsername()
if requestUserId == "" && code == "" {
c.ResponseError(c.T("general:Please login first"), "Please login first")
@ -494,12 +483,7 @@ func (c *ApiController) SetPassword() {
c.ResponseError(c.T("general:Missing parameter"))
return
}
if userId != c.GetSession("verifiedUserId") {
c.ResponseError(c.T("general:Wrong userId"))
return
}
c.SetSession("verifiedCode", "")
c.SetSession("verifiedUserId", "")
}
targetUser, err := object.GetUser(userId)
@ -522,11 +506,7 @@ func (c *ApiController) SetPassword() {
}
}
} else if code == "" {
if user.Ldap == "" {
err = object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
} else {
err = object.CheckLdapUserPassword(targetUser, oldPassword, c.GetAcceptLanguage())
}
err = object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
if err != nil {
c.ResponseError(err.Error())
return
@ -549,34 +529,11 @@ func (c *ApiController) SetPassword() {
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.UpdateUserPassword(organization)
targetUser.NeedUpdatePassword = false
targetUser.LastChangePasswordTime = util.GetCurrentTime()
if user.Ldap == "" {
_, err = object.UpdateUser(userId, targetUser, []string{"password", "need_update_password", "password_type", "last_change_password_time"}, false)
} else {
err = object.ResetLdapPassword(targetUser, newPassword, c.GetAcceptLanguage())
}
_, err = object.UpdateUser(userId, targetUser, []string{"password", "need_update_password", "password_type"}, false)
if err != nil {
c.ResponseError(err.Error())
return

View File

@ -45,15 +45,6 @@ func (c *ApiController) ResponseOk(data ...interface{}) {
// ResponseError ...
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, "不存在") {
@ -294,18 +285,12 @@ func checkQuotaForProvider(count int) error {
return nil
}
func checkQuotaForUser() error {
func checkQuotaForUser(count int) error {
quota := conf.GetConfigQuota().User
if quota == -1 {
return nil
}
count, err := object.GetUserCount("", "", "", "")
if err != nil {
return err
}
if int(count) >= quota {
if count >= quota {
return fmt.Errorf("user quota is exceeded")
}
return nil

View File

@ -132,8 +132,7 @@ func (c *ApiController) SendVerificationCode() {
c.ResponseError(err.Error())
return
}
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
remoteAddr := util.GetIPFromRequest(c.Ctx.Request)
if msg := vform.CheckParameter(form.SendVerifyCode, c.GetAcceptLanguage()); msg != "" {
c.ResponseError(msg)
@ -246,6 +245,8 @@ func (c *ApiController) SendVerificationCode() {
if user != nil && util.GetMaskedEmail(mfaProps.Secret) == vform.Dest {
vform.Dest = mfaProps.Secret
}
} else if vform.Method == MfaSetupVerification {
c.SetSession(MfaDestSession, vform.Dest)
}
provider, err = application.GetEmailProvider(vform.Method)
@ -258,7 +259,7 @@ func (c *ApiController) SendVerificationCode() {
return
}
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, clientIp, vform.Dest)
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, vform.Dest)
case object.VerifyTypePhone:
if vform.Method == LoginVerification || vform.Method == ForgetVerification {
if user != nil && util.GetMaskedPhone(user.Phone) == vform.Dest {
@ -280,6 +281,11 @@ func (c *ApiController) SendVerificationCode() {
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 {
mfaProps := user.GetPreferredMfaProps(false)
if user != nil && util.GetMaskedPhone(mfaProps.Secret) == vform.Dest {
@ -287,7 +293,6 @@ func (c *ApiController) SendVerificationCode() {
}
vform.CountryCode = mfaProps.CountryCode
vform.CountryCode = user.GetCountryCode(vform.CountryCode)
}
provider, err = application.GetSmsProvider(vform.Method, vform.CountryCode)
@ -304,7 +309,7 @@ func (c *ApiController) SendVerificationCode() {
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), vform.CountryCode))
return
} else {
sendResp = object.SendVerificationCodeToPhone(organization, user, provider, clientIp, phone)
sendResp = object.SendVerificationCodeToPhone(organization, user, provider, remoteAddr, phone)
}
}
@ -527,6 +532,5 @@ func (c *ApiController) VerifyCode() {
}
c.SetSession("verifiedCode", authForm.Code)
c.SetSession("verifiedUserId", user.GetId())
c.ResponseOk()
}

View File

@ -16,9 +16,7 @@ package email
import (
"crypto/tls"
"strings"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/gomail/v2"
)
@ -35,13 +33,6 @@ func NewSmtpEmailProvider(userName string, password string, host string, port in
dialer.SSL = !disableSsl
if strings.HasSuffix(host, ".amazonaws.com") {
socks5Proxy := conf.GetConfigString("socks5Proxy")
if socks5Proxy != "" {
dialer.SetSocks5Proxy(socks5Proxy)
}
}
return &SmtpEmailProvider{Dialer: dialer}
}

View File

@ -26,10 +26,6 @@ type AuthForm struct {
Name string `json:"name"`
FirstName string `json:"firstName"`
LastName string `json:"lastName"`
Gender string `json:"gender"`
Bio string `json:"bio"`
Tag string `json:"tag"`
Education string `json:"education"`
Email string `json:"email"`
Phone string `json:"phone"`
Affiliation string `json:"affiliation"`

13
go.mod
View File

@ -9,10 +9,9 @@ require (
github.com/beego/beego v1.12.12
github.com/beevik/etree v1.1.0
github.com/casbin/casbin/v2 v2.77.2
github.com/casdoor/go-sms-sender v0.25.0
github.com/casdoor/gomail/v2 v2.1.0
github.com/casdoor/ldapserver v1.2.0
github.com/casdoor/notify v1.0.0
github.com/casdoor/go-sms-sender v0.24.0
github.com/casdoor/gomail/v2 v2.0.1
github.com/casdoor/notify v0.45.0
github.com/casdoor/oss v1.8.0
github.com/casdoor/xorm-adapter/v3 v3.1.0
github.com/casvisor/casvisor-go-sdk v1.4.0
@ -21,6 +20,7 @@ require (
github.com/elazarl/go-bindata-assetfs v1.0.1 // indirect
github.com/elimity-com/scim v0.0.0-20230426070224-941a5eac92f3
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-git/go-git/v5 v5.11.0
github.com/go-ldap/ldap/v3 v3.4.6
@ -60,10 +60,9 @@ require (
github.com/xorm-io/core v0.7.4
github.com/xorm-io/xorm v1.1.6
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/crypto v0.32.0
golang.org/x/net v0.34.0
golang.org/x/crypto v0.21.0
golang.org/x/net v0.21.0
golang.org/x/oauth2 v0.17.0
golang.org/x/text v0.21.0
google.golang.org/api v0.150.0
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/square/go-jose.v2 v2.6.0

49
go.sum
View File

@ -1087,14 +1087,12 @@ github.com/casdoor/casdoor-go-sdk v0.50.0 h1:bUYbz/MzJuWfLKJbJM0+U0YpYewAur+THp5
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/go.mod h1:eagkvwlZ4Hcsuc/uQsLHYEulz5jN65SVSwV/AIE7zsc=
github.com/casdoor/go-sms-sender v0.25.0 h1:eF4cOCSbjVg7+0uLlJQnna/FQ0BWW+Fp/x4cXhzQu1Y=
github.com/casdoor/go-sms-sender v0.25.0/go.mod h1:bOm4H8/YfJmEHjBatEVQFOnAf0OOn1B0Wi5B7zDhws0=
github.com/casdoor/gomail/v2 v2.1.0 h1:ua97E3CARnF1Ik8ga/Drz9uGZfaElXJumFexiErWUxM=
github.com/casdoor/gomail/v2 v2.1.0/go.mod h1:GFzOD9RhY0nODiiPaQiOa6DfoKtmO9aTesu5qrp26OI=
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 v1.0.0 h1:oldsaaQFPrlufm/OA314z8DwFVE1Tc9Gt1z4ptRHhXw=
github.com/casdoor/notify v1.0.0/go.mod h1:wNHQu0tiDROMBIvz0j3Om3Lhd5yZ+AIfnFb8MYb8OLQ=
github.com/casdoor/go-sms-sender v0.24.0 h1:LNLsce3EG/87I3JS6UiajF3LlQmdIiCgebEu0IE4wSM=
github.com/casdoor/go-sms-sender v0.24.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/go.mod h1:VnGPslEAtpix5FjHisR/WKB1qvZDBaujbikxDe9d+2Q=
github.com/casdoor/notify v0.45.0 h1:OlaFvcQFjGOgA4mRx07M8AH1gvb5xNo21mcqrVGlLgk=
github.com/casdoor/notify v0.45.0/go.mod h1:wNHQu0tiDROMBIvz0j3Om3Lhd5yZ+AIfnFb8MYb8OLQ=
github.com/casdoor/oss v1.8.0 h1:uuyKhDIp7ydOtV4lpqhAY23Ban2Ln8La8+QT36CwylM=
github.com/casdoor/oss v1.8.0/go.mod h1:uaqO7KBI2lnZcnB8rF7O6C2bN7llIbfC5Ql8ex1yR1U=
github.com/casdoor/xorm-adapter/v3 v3.1.0 h1:NodWayRtSLVSeCvL9H3Hc61k0G17KhV9IymTCNfh3kk=
@ -1239,6 +1237,8 @@ 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.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
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/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
@ -2163,10 +2163,8 @@ golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20181106170214-d68db9428509/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -2232,10 +2230,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20171115151908-9dfe39835686/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -2323,10 +2319,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -2381,11 +2375,8 @@ golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -2512,11 +2503,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@ -2536,10 +2524,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -2560,10 +2546,8 @@ golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -2650,9 +2634,8 @@ golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM=
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -1,167 +1,167 @@
{
"account": {
"Failed to add user": "عدم موفقیت در افزودن کاربر",
"Get init score failed, error: %w": "عدم موفقیت در دریافت امتیاز اولیه، خطا: %w",
"Please sign out first": "لطفاً ابتدا خارج شوید",
"The application does not allow to sign up new account": "برنامه اجازه ثبت‌نام حساب جدید را نمی‌دهد"
"Failed to add user": "Failed to add user",
"Get init score failed, error: %w": "Get init score failed, error: %w",
"Please sign out first": "Please sign out first",
"The application does not allow to sign up new account": "The application does not allow to sign up new account"
},
"auth": {
"Challenge method should be S256": "روش چالش باید S256 باشد",
"Failed to create user, user information is invalid: %s": "عدم موفقیت در ایجاد کاربر، اطلاعات کاربر نامعتبر است: %s",
"Failed to login in: %s": "عدم موفقیت در ورود: %s",
"Invalid token": "توکن نامعتبر",
"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": "حساب برای ارائه‌دهنده: %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": "حساب برای ارائه‌دهنده: %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 application: %s does not exist": "برنامه: %s وجود ندارد",
"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 email 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 organization: %s does not exist": "سازمان: %s وجود ندارد",
"The provider: %s is not enabled for the application": "ارائه‌دهنده: %s برای برنامه فعال نیست",
"Unauthorized operation": "عملیات غیرمجاز",
"Unknown authentication type (not password or provider), form = %s": "نوع احراز هویت ناشناخته (نه رمز عبور و نه ارائه‌دهنده)، فرم = %s",
"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": "کاربر پرداختی %s اشتراک فعال یا در انتظار ندارد و برنامه: %s قیمت‌گذاری پیش‌فرض ندارد"
"Challenge method should be S256": "Challenge method should be S256",
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s",
"Failed to login in: %s": "Failed to login in: %s",
"Invalid token": "Invalid token",
"State expected: %s, but got: %s": "State expected: %s, but got: %s",
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up",
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
"The application: %s does not exist": "The application: %s does not exist",
"The login method: login with LDAP is not enabled for the application": "The login method: login with LDAP 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 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 provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
"Unauthorized operation": "Unauthorized operation",
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags",
"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"
},
"cas": {
"Service %s and %s do not match": "سرویس %s و %s مطابقت ندارند"
"Service %s and %s do not match": "Service %s and %s do not match"
},
"check": {
"Affiliation cannot be blank": "وابستگی نمی‌تواند خالی باشد",
"Default code does not match the code's matching rules": "کد پیش‌فرض با قوانین تطبیق کد مطابقت ندارد",
"DisplayName cannot be blank": "نام نمایشی نمی‌تواند خالی باشد",
"DisplayName is not valid real name": "نام نمایشی یک نام واقعی معتبر نیست",
"Email already exists": "ایمیل قبلاً وجود دارد",
"Email cannot be empty": "ایمیل نمی‌تواند خالی باشد",
"Email is invalid": "ایمیل نامعتبر است",
"Empty username.": "نام کاربری خالی است.",
"Face data does not exist, cannot log in": "داده‌های چهره وجود ندارد، نمی‌توان وارد شد",
"Face data mismatch": "عدم تطابق داده‌های چهره",
"FirstName cannot be blank": "نام نمی‌تواند خالی باشد",
"Invitation code cannot be blank": "کد دعوت نمی‌تواند خالی باشد",
"Invitation code exhausted": "کد دعوت استفاده شده است",
"Invitation code is invalid": "کد دعوت نامعتبر است",
"Invitation code suspended": "کد دعوت معلق است",
"LDAP user name or password incorrect": "نام کاربری یا رمز عبور LDAP نادرست است",
"LastName cannot be blank": "نام خانوادگی نمی‌تواند خالی باشد",
"Multiple accounts with same uid, please check your ldap server": "چندین حساب با uid یکسان، لطفاً سرور LDAP خود را بررسی کنید",
"Organization does not exist": "سازمان وجود ندارد",
"Phone already exists": "تلفن قبلاً وجود دارد",
"Phone cannot be empty": "تلفن نمی‌تواند خالی باشد",
"Phone number is invalid": "شماره تلفن نامعتبر است",
"Please register using the email 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": "لطفاً با استفاده از نام کاربری مربوط به کد دعوت ثبت‌نام کنید",
"Session outdated, please login again": "جلسه منقضی شده است، لطفاً دوباره وارد شوید",
"The invitation code has already been used": "کد دعوت قبلاً استفاده شده است",
"The user is forbidden to sign in, please contact the administrator": "ورود کاربر ممنوع است، لطفاً با مدیر تماس بگیرید",
"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 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\"": "مقدار \"%s\" برای فیلد ثبت‌نام \"%s\" با عبارت منظم مورد ثبت‌نام برنامه \"%s\" مطابقت ندارد",
"Username already exists": "نام کاربری قبلاً وجود دارد",
"Username cannot be an email address": "نام کاربری نمی‌تواند یک آدرس ایمیل باشد",
"Username cannot contain white spaces": "نام کاربری نمی‌تواند حاوی فاصله باشد",
"Username cannot start with a digit": "نام کاربری نمی‌تواند با یک رقم شروع شود",
"Username is too long (maximum is 39 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": "شما رمز عبور یا کد اشتباه را بیش از حد وارد کرده‌اید، لطفاً %d دقیقه صبر کنید و دوباره تلاش کنید",
"Your region is not allow to signup by phone": "منطقه شما اجازه ثبت‌نام با تلفن را ندارد",
"password or code is incorrect": "رمز عبور یا کد نادرست است",
"password or code is incorrect, you have %d remaining chances": "رمز عبور یا کد نادرست است، شما %d فرصت باقی‌مانده دارید",
"unsupported password type: %s": "نوع رمز عبور پشتیبانی نشده: %s"
"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",
"DisplayName cannot be blank": "DisplayName cannot be blank",
"DisplayName is not valid real name": "DisplayName is not valid real name",
"Email already exists": "Email already exists",
"Email cannot be empty": "Email cannot be empty",
"Email is invalid": "Email is invalid",
"Empty username.": "Empty username.",
"Face data does not exist, cannot log in": "Face data does not exist, cannot log in",
"Face data mismatch": "Face data mismatch",
"FirstName cannot be blank": "FirstName cannot be blank",
"Invitation code cannot be blank": "Invitation code cannot be blank",
"Invitation code exhausted": "Invitation code exhausted",
"Invitation code is invalid": "Invitation code is invalid",
"Invitation code suspended": "Invitation code suspended",
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
"LastName cannot be blank": "LastName cannot be blank",
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
"Organization does not exist": "Organization does not exist",
"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 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",
"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 user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.",
"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 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\\\"",
"Username already exists": "Username already exists",
"Username cannot be an email address": "Username cannot be an email address",
"Username cannot contain white spaces": "Username cannot contain white spaces",
"Username cannot start with a digit": "Username cannot start with a digit",
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
"Username must have at least 2 characters": "Username must have at least 2 characters",
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
"Your region is not allow to signup by phone": "Your region is not allow to signup by phone",
"password or code is incorrect": "password or code is incorrect",
"password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
"unsupported password type: %s": "unsupported password type: %s"
},
"general": {
"Missing parameter": "پارامتر گمشده",
"Please login first": "لطفاً ابتدا وارد شوید",
"The organization: %s should have one application at least": "سازمان: %s باید حداقل یک برنامه داشته باشد",
"The user: %s doesn't exist": "کاربر: %s وجود ندارد",
"don't support captchaProvider: ": "از captchaProvider پشتیبانی نمی‌شود: ",
"this operation is not allowed in demo mode": "این عملیات در حالت دمو مجاز نیست",
"this operation requires administrator to perform": "این عملیات نیاز به مدیر برای انجام دارد"
"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 user: %s doesn't exist": "The user: %s doesn't exist",
"don't support captchaProvider: ": "don't support captchaProvider: ",
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode",
"this operation requires administrator to perform": "this operation requires administrator to perform"
},
"ldap": {
"Ldap server exist": "سرور LDAP وجود دارد"
"Ldap server exist": "Ldap server exist"
},
"link": {
"Please link first": "لطفاً ابتدا پیوند دهید",
"This application has no providers": "این برنامه ارائه‌دهنده‌ای ندارد",
"This application has no providers of type": "این برنامه ارائه‌دهنده‌ای از نوع ندارد",
"This provider can't be unlinked": "این ارائه‌دهنده نمی‌تواند لغو پیوند شود",
"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": "شما نمی‌توانید خودتان را لغو پیوند کنید، شما عضو هیچ برنامه‌ای نیستید"
"Please link first": "Please link first",
"This application has no providers": "This application has no providers",
"This application has no providers of type": "This application has no providers of type",
"This provider can't be unlinked": "This provider can't be unlinked",
"You are not the global admin, you can't unlink other users": "You are not the global admin, you can't unlink other users",
"You can't unlink yourself, you are not a member of any application": "You can't unlink yourself, you are not a member of any application"
},
"organization": {
"Only admin can modify the %s.": "فقط مدیر می‌تواند %s را تغییر دهد.",
"The %s is immutable.": "%s غیرقابل تغییر است.",
"Unknown modify rule %s.": "قانون تغییر ناشناخته %s."
"Only admin can modify the %s.": "Only admin can modify the %s.",
"The %s is immutable.": "The %s is immutable.",
"Unknown modify rule %s.": "Unknown modify rule %s."
},
"permission": {
"The permission: \"%s\" doesn't exist": "مجوز: \"%s\" وجود ندارد"
"The permission: \\\"%s\\\" doesn't exist": "The permission: \\\"%s\\\" doesn't exist"
},
"provider": {
"Invalid application id": "شناسه برنامه نامعتبر",
"the provider: %s does not exist": "ارائه‌دهنده: %s وجود ندارد"
"Invalid application id": "Invalid application id",
"the provider: %s does not exist": "the provider: %s does not exist"
},
"resource": {
"User is nil for tag: avatar": "کاربر برای برچسب: آواتار تهی است",
"Username or fullFilePath is empty: username = %s, fullFilePath = %s": "نام کاربری یا مسیر کامل فایل خالی است: نام کاربری = %s، مسیر کامل فایل = %s"
"User is nil for tag: avatar": "User is nil for tag: avatar",
"Username or fullFilePath is empty: username = %s, fullFilePath = %s": "Username or fullFilePath is empty: username = %s, fullFilePath = %s"
},
"saml": {
"Application %s not found": "برنامه %s یافت نشد"
"Application %s not found": "Application %s not found"
},
"saml_sp": {
"provider %s's category is not SAML": "دسته‌بندی ارائه‌دهنده %s SAML نیست"
"provider %s's category is not SAML": "provider %s's category is not SAML"
},
"service": {
"Empty parameters for emailForm: %v": "پارامترهای خالی برای emailForm: %v",
"Invalid Email receivers: %s": "گیرندگان ایمیل نامعتبر: %s",
"Invalid phone receivers: %s": "گیرندگان تلفن نامعتبر: %s"
"Empty parameters for emailForm: %v": "Empty parameters for emailForm: %v",
"Invalid Email receivers: %s": "Invalid Email receivers: %s",
"Invalid phone receivers: %s": "Invalid phone receivers: %s"
},
"storage": {
"The objectKey: %s is not allowed": "objectKey: %s مجاز نیست",
"The provider type: %s is not supported": "نوع ارائه‌دهنده: %s پشتیبانی نمی‌شود"
"The objectKey: %s is not allowed": "The objectKey: %s is not allowed",
"The provider type: %s is not supported": "The provider type: %s is not supported"
},
"token": {
"Grant_type: %s is not supported in this application": "grant_type: %s در این برنامه پشتیبانی نمی‌شود",
"Invalid application or wrong clientSecret": "برنامه نامعتبر یا clientSecret نادرست",
"Invalid client_id": "client_id نامعتبر",
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "آدرس بازگشت: %s در لیست آدرس‌های بازگشت مجاز وجود ندارد",
"Token not found, invalid accessToken": "توکن یافت نشد، accessToken نامعتبر"
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application",
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret",
"Invalid client_id": "Invalid client_id",
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list",
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
},
"user": {
"Display name cannot be empty": "نام نمایشی نمی‌تواند خالی باشد",
"New password cannot contain blank space.": "رمز عبور جدید نمی‌تواند حاوی فاصله خالی باشد."
"Display name cannot be empty": "Display name cannot be empty",
"New password cannot contain blank space.": "New password cannot contain blank space."
},
"user_upload": {
"Failed to import users": "عدم موفقیت در وارد کردن کاربران"
"Failed to import users": "Failed to import users"
},
"util": {
"No application is found for userId: %s": "هیچ برنامه‌ای برای userId: %s یافت نشد",
"No provider for category: %s is found for application: %s": "هیچ ارائه‌دهنده‌ای برای دسته‌بندی: %s برای برنامه: %s یافت نشد",
"The provider: %s is not found": "ارائه‌دهنده: %s یافت نشد"
"No application is found for userId: %s": "No application is found for userId: %s",
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
"The provider: %s is not found": "The provider: %s is not found"
},
"verification": {
"Invalid captcha provider.": "ارائه‌دهنده کپچا نامعتبر.",
"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, or has already been used!": "کد تأیید هنوز ارسال نشده است، یا قبلاً استفاده شده است!",
"Turing test failed.": "تست تورینگ ناموفق بود.",
"Unable to get the email modify rule.": "عدم توانایی در دریافت قانون تغییر ایمیل.",
"Unable to get the phone modify rule.": "عدم توانایی در دریافت قانون تغییر تلفن.",
"Unknown type": "نوع ناشناخته",
"Wrong verification code!": "کد تأیید اشتباه!",
"You should verify your code in %d min!": "شما باید کد خود را در %d دقیقه تأیید کنید!",
"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": "لطفاً یک ارائه‌دهنده ایمیل به لیست \"ارائه‌دهندگان\" برای برنامه: %s اضافه کنید",
"the user does not exist, please sign up first": "کاربر وجود ندارد، لطفاً ابتدا ثبت‌نام کنید"
"Invalid captcha provider.": "Invalid captcha provider.",
"Phone number is invalid in your region %s": "Phone number is invalid in your region %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, 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.",
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
"Unknown type": "Unknown type",
"Wrong verification code!": "Wrong verification code!",
"You should verify your code in %d min!": "You should verify your code in %d min!",
"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 an Email provider to the \\\"Providers\\\" list for the application: %s": "please add an Email provider to the \\\"Providers\\\" list for the application: %s",
"the user does not exist, please sign up first": "the user does not exist, please sign up first"
},
"webauthn": {
"Found no credentials for this user": "هیچ اعتباری برای این کاربر یافت نشد",
"Please call WebAuthnSigninBegin first": "لطفاً ابتدا WebAuthnSigninBegin را فراخوانی کنید"
"Found no credentials for this user": "Found no credentials for this user",
"Please call WebAuthnSigninBegin first": "Please call WebAuthnSigninBegin first"
}
}

View File

@ -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) is already linked to another account: %s (%s)": "Аккаунт поставщика: %s и имя пользователя: %s (%s) уже связаны с другим аккаунтом: %s (%s)",
"The application: %s does not exist": "Приложение: %s не существует",
"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": "Метод входа: вход с помощью SMS не включен для приложения",
"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 LDAP is not enabled for the application": "The login method: login with LDAP 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 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 organization: %s does not exist": "The organization: %s does not exist",
"The provider: %s is not enabled for the application": "Провайдер: %s не включен для приложения",
@ -53,16 +53,16 @@
"Phone already exists": "Телефон уже существует",
"Phone cannot be empty": "Телефон не может быть пустым",
"Phone number is invalid": "Номер телефона является недействительным",
"Please register using the email 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 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 username corresponding to the invitation code": "Please register using the username corresponding to the invitation code",
"Session outdated, please login again": "Сессия устарела, пожалуйста, войдите снова",
"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: %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 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\\\"": "Значение \\\"%s\\\" поля регистрации \\\"%s\\\" не соответствует регулярному выражению приложения \\\"%s\\\"",
"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 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\\\"",
"Username already exists": "Имя пользователя уже существует",
"Username cannot be an email address": "Имя пользователя не может быть адресом электронной почты",
"Username cannot contain white spaces": "Имя пользователя не может содержать пробелы",
@ -78,11 +78,11 @@
"general": {
"Missing parameter": "Отсутствующий параметр",
"Please login first": "Пожалуйста, сначала войдите в систему",
"The organization: %s should have one application at least": "Организация: %s должна иметь хотя бы одно приложение",
"The organization: %s should have one application at least": "The organization: %s should have one application at least",
"The user: %s doesn't exist": "Пользователь %s не существует",
"don't support captchaProvider: ": "неподдерживаемый captchaProvider: ",
"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 server exist": "LDAP-сервер существует"
@ -101,11 +101,11 @@
"Unknown modify rule %s.": "Неизвестное изменение правила %s."
},
"permission": {
"The permission: \\\"%s\\\" doesn't exist": "Разрешение: \\\"%s\\\" не существует"
"The permission: \\\"%s\\\" doesn't exist": "The permission: \\\"%s\\\" doesn't exist"
},
"provider": {
"Invalid application id": "Неверный идентификатор приложения",
"the provider: %s does not exist": "Провайдер: %s не существует"
"the provider: %s does not exist": "провайдер: %s не существует"
},
"resource": {
"User is nil for tag: avatar": "Пользователь равен нулю для тега: аватар",
@ -115,7 +115,7 @@
"Application %s not found": "Приложение %s не найдено"
},
"saml_sp": {
"provider %s's category is not SAML": "Категория провайдера %s не является SAML"
"provider %s's category is not SAML": "категория провайдера %s не является SAML"
},
"service": {
"Empty parameters for emailForm: %v": "Пустые параметры для emailForm: %v",
@ -148,7 +148,7 @@
"verification": {
"Invalid captcha provider.": "Недействительный поставщик CAPTCHA.",
"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!",
"Turing test failed.": "Тест Тьюринга не удался.",
"Unable to get the email modify rule.": "Невозможно получить правило изменения электронной почты.",
@ -156,8 +156,8 @@
"Unknown type": "Неизвестный тип",
"Wrong verification code!": "Неправильный код подтверждения!",
"You should verify your code in %d min!": "Вы должны проверить свой код через %d минут!",
"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": "Пожалуйста, добавьте поставщика электронной почты в список \\\"Провайдеры\\\" для приложения: %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",
"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",
"the user does not exist, please sign up first": "Пользователь не существует, пожалуйста, сначала зарегистрируйтесь"
},
"webauthn": {

View File

@ -200,7 +200,7 @@ func (idp *AlipayIdProvider) postWithBody(body interface{}, targetUrl string) ([
formData.Set("sign", sign)
resp, err := idp.Client.Post(targetUrl, "application/x-www-form-urlencoded;charset=utf-8", strings.NewReader(formData.Encode()))
resp, err := idp.Client.PostForm(targetUrl, formData)
if err != nil {
return nil, err
}

View File

@ -188,23 +188,10 @@ type GitHubUserInfo struct {
} `json:"plan"`
}
type GitHubUserEmailInfo struct {
Email string `json:"email"`
Primary bool `json:"primary"`
Verified bool `json:"verified"`
Visibility string `json:"visibility"`
}
type GitHubErrorInfo struct {
Message string `json:"message"`
DocumentationUrl string `json:"documentation_url"`
Status string `json:"status"`
}
func (idp *GithubIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
req, err := http.NewRequest("GET", "https://api.github.com/user", nil)
if err != nil {
return nil, err
panic(err)
}
req.Header.Add("Authorization", "token "+token.AccessToken)
resp, err := idp.Client.Do(req)
@ -225,42 +212,6 @@ func (idp *GithubIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
return nil, err
}
if githubUserInfo.Email == "" {
reqEmail, err := http.NewRequest("GET", "https://api.github.com/user/emails", nil)
if err != nil {
return nil, err
}
reqEmail.Header.Add("Authorization", "token "+token.AccessToken)
respEmail, err := idp.Client.Do(reqEmail)
if err != nil {
return nil, err
}
defer respEmail.Body.Close()
emailBody, err := io.ReadAll(respEmail.Body)
if err != nil {
return nil, err
}
if respEmail.StatusCode != 200 {
var errMessage GitHubErrorInfo
err = json.Unmarshal(emailBody, &errMessage)
if err != nil {
return nil, err
}
fmt.Printf("GithubIdProvider:GetUserInfo() error, status code = %d, error message = %v\n", respEmail.StatusCode, errMessage)
} else {
var userEmails []GitHubUserEmailInfo
err = json.Unmarshal(emailBody, &userEmails)
if err != nil {
return nil, err
}
githubUserInfo.Email = idp.getEmailFromEmailsResult(userEmails)
}
}
userInfo := UserInfo{
Id: strconv.Itoa(githubUserInfo.Id),
Username: githubUserInfo.Login,
@ -297,27 +248,3 @@ func (idp *GithubIdProvider) postWithBody(body interface{}, url string) ([]byte,
return data, nil
}
func (idp *GithubIdProvider) getEmailFromEmailsResult(emailInfo []GitHubUserEmailInfo) string {
primaryEmail := ""
verifiedEmail := ""
for _, addr := range emailInfo {
if !addr.Verified || strings.Contains(addr.Email, "users.noreply.github.com") {
continue
}
if addr.Primary {
primaryEmail = addr.Email
break
} else if verifiedEmail == "" {
verifiedEmail = addr.Email
}
}
if primaryEmail != "" {
return primaryEmail
}
return verifiedEmail
}

View File

@ -1,161 +0,0 @@
// 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 idp
import (
"encoding/json"
"fmt"
"io"
"net/http"
"time"
"golang.org/x/oauth2"
)
type KwaiIdProvider struct {
Client *http.Client
Config *oauth2.Config
}
func NewKwaiIdProvider(clientId string, clientSecret string, redirectUrl string) *KwaiIdProvider {
idp := &KwaiIdProvider{}
idp.Config = idp.getConfig(clientId, clientSecret, redirectUrl)
return idp
}
func (idp *KwaiIdProvider) SetHttpClient(client *http.Client) {
idp.Client = client
}
func (idp *KwaiIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
endpoint := oauth2.Endpoint{
TokenURL: "https://open.kuaishou.com/oauth2/access_token",
AuthURL: "https://open.kuaishou.com/oauth2/authorize", // qr code: /oauth2/connect
}
config := &oauth2.Config{
Scopes: []string{"user_info"},
Endpoint: endpoint,
ClientID: clientId,
ClientSecret: clientSecret,
RedirectURL: redirectUrl,
}
return config
}
type KwaiTokenResp struct {
Result int `json:"result"`
ErrorMsg string `json:"error_msg"`
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
RefreshToken string `json:"refresh_token"`
RefreshTokenExpiresIn int `json:"refresh_token_expires_in"`
OpenId string `json:"open_id"`
Scopes []string `json:"scopes"`
}
// GetToken use code to get access_token
func (idp *KwaiIdProvider) GetToken(code string) (*oauth2.Token, error) {
params := map[string]string{
"app_id": idp.Config.ClientID,
"app_secret": idp.Config.ClientSecret,
"code": code,
"grant_type": "authorization_code",
}
tokenUrl := fmt.Sprintf("%s?app_id=%s&app_secret=%s&code=%s&grant_type=authorization_code",
idp.Config.Endpoint.TokenURL, params["app_id"], params["app_secret"], params["code"])
resp, err := idp.Client.Get(tokenUrl)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var tokenResp KwaiTokenResp
err = json.Unmarshal(body, &tokenResp)
if err != nil {
return nil, err
}
if tokenResp.Result != 1 {
return nil, fmt.Errorf("get token error: %s", tokenResp.ErrorMsg)
}
token := &oauth2.Token{
AccessToken: tokenResp.AccessToken,
RefreshToken: tokenResp.RefreshToken,
Expiry: time.Now().Add(time.Duration(tokenResp.ExpiresIn) * time.Second),
}
raw := make(map[string]interface{})
raw["open_id"] = tokenResp.OpenId
token = token.WithExtra(raw)
return token, nil
}
// More details: https://open.kuaishou.com/openapi/user_info
type KwaiUserInfo struct {
Result int `json:"result"`
ErrorMsg string `json:"error_msg"`
UserInfo struct {
Head string `json:"head"`
Name string `json:"name"`
Sex string `json:"sex"`
City string `json:"city"`
} `json:"user_info"`
}
// GetUserInfo use token to get user profile
func (idp *KwaiIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
userInfoUrl := fmt.Sprintf("https://open.kuaishou.com/openapi/user_info?app_id=%s&access_token=%s",
idp.Config.ClientID, token.AccessToken)
resp, err := idp.Client.Get(userInfoUrl)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var kwaiUserInfo KwaiUserInfo
err = json.Unmarshal(body, &kwaiUserInfo)
if err != nil {
return nil, err
}
if kwaiUserInfo.Result != 1 {
return nil, fmt.Errorf("get user info error: %s", kwaiUserInfo.ErrorMsg)
}
userInfo := &UserInfo{
Id: token.Extra("open_id").(string),
Username: kwaiUserInfo.UserInfo.Name,
DisplayName: kwaiUserInfo.UserInfo.Name,
AvatarUrl: kwaiUserInfo.UserInfo.Head,
Extra: map[string]string{
"gender": kwaiUserInfo.UserInfo.Sex,
"city": kwaiUserInfo.UserInfo.City,
},
}
return userInfo, nil
}

View File

@ -113,8 +113,6 @@ func GetIdProvider(idpInfo *ProviderInfo, redirectUrl string) (IdProvider, error
return NewOktaIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl), nil
case "Douyin":
return NewDouyinIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "Kwai":
return NewKwaiIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "Bilibili":
return NewBilibiliIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "MetaMask":

View File

@ -15,81 +15,33 @@
package ldap
import (
"crypto/tls"
"fmt"
"hash/fnv"
"log"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/object"
ldap "github.com/casdoor/ldapserver"
ldap "github.com/forestmgy/ldapserver"
"github.com/lor00x/goldap/message"
)
func StartLdapServer() {
ldapServerPort := conf.GetConfigString("ldapServerPort")
ldapsServerPort := conf.GetConfigString("ldapsServerPort")
if ldapServerPort == "" || ldapServerPort == "0" {
return
}
server := ldap.NewServer()
serverSsl := ldap.NewServer()
routes := ldap.NewRouteMux()
routes.Bind(handleBind)
routes.Search(handleSearch).Label(" SEARCH****")
server.Handle(routes)
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)
err := server.ListenAndServe("0.0.0.0:" + ldapServerPort)
if err != nil {
return nil, err
log.Printf("StartLdapServer() failed, err = %s", err.Error())
}
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) {
@ -190,7 +142,7 @@ func handleSearch(w ldap.ResponseWriter, m *ldap.Message) {
}
for _, attr := range attrs {
e.AddAttribute(message.AttributeDescription(attr), getAttribute(string(attr), user))
if string(attr) == "title" {
if string(attr) == "cn" {
e.AddAttribute(message.AttributeDescription(attr), getAttribute("title", user))
}
}

View File

@ -23,7 +23,7 @@ import (
"github.com/casdoor/casdoor/util"
"github.com/lor00x/goldap/message"
ldap "github.com/casdoor/ldapserver"
ldap "github.com/forestmgy/ldapserver"
"github.com/xorm-io/builder"
)

View File

@ -56,7 +56,6 @@ func main() {
beego.InsertFilter("*", beego.BeforeRouter, routers.StaticFilter)
beego.InsertFilter("*", beego.BeforeRouter, routers.AutoSigninFilter)
beego.InsertFilter("*", beego.BeforeRouter, routers.CorsFilter)
beego.InsertFilter("*", beego.BeforeRouter, routers.TimeoutFilter)
beego.InsertFilter("*", beego.BeforeRouter, routers.ApiFilter)
beego.InsertFilter("*", beego.BeforeRouter, routers.PrometheusFilter)
beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage)
@ -72,7 +71,6 @@ func main() {
beego.BConfig.WebConfig.Session.SessionProviderConfig = conf.GetConfigString("redisEndpoint")
}
beego.BConfig.WebConfig.Session.SessionCookieLifeTime = 3600 * 24 * 30
beego.BConfig.WebConfig.Session.SessionGCMaxLifetime = 3600 * 24 * 30
// beego.BConfig.WebConfig.Session.SessionCookieSameSite = http.SameSiteNoneMode
err := logs.SetLogger(logs.AdapterFile, conf.GetConfigString("logConfig"))
@ -83,11 +81,6 @@ func main() {
// logs.SetLevel(logs.LevelInformational)
logs.SetLogFuncCall(false)
err = util.StopOldInstance(port)
if err != nil {
panic(err)
}
go ldap.StartLdapServer()
go radius.StartRadiusServer()
go object.ClearThroughputPerSecond()

View File

@ -1,29 +0,0 @@
// Copyright 2025 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package notification
import (
"github.com/casdoor/notify"
"github.com/casdoor/notify/service/cucloud"
)
func NewCucloudProvider(accessKey, secretKey, topicName, messageTitle, cloudRegionCode, accountId, notifyType string) (notify.Notifier, error) {
cucloud := cucloud.New(accessKey, secretKey, topicName, messageTitle, cloudRegionCode, accountId, notifyType)
notifier := notify.New()
notifier.UseServices(cucloud)
return notifier, nil
}

View File

@ -16,7 +16,7 @@ package notification
import "github.com/casdoor/notify"
func GetNotificationProvider(typ string, clientId string, clientSecret string, clientId2 string, clientSecret2 string, appId string, receiver string, method string, title string, metaData string, regionId string) (notify.Notifier, error) {
func GetNotificationProvider(typ string, clientId string, clientSecret string, clientId2 string, clientSecret2 string, appId string, receiver string, method string, title string, metaData string) (notify.Notifier, error) {
if typ == "Telegram" {
return NewTelegramProvider(clientSecret, receiver)
} else if typ == "Custom HTTP" {
@ -53,8 +53,6 @@ func GetNotificationProvider(typ string, clientId string, clientSecret string, c
return NewRocketChatProvider(clientId, clientSecret, appId, receiver)
} else if typ == "Viber" {
return NewViberProvider(clientId, clientSecret, appId, receiver)
} else if typ == "CUCloud" {
return NewCucloudProvider(clientId, clientSecret, appId, title, regionId, clientId2, metaData)
}
return nil, nil

View File

@ -31,17 +31,15 @@ type SigninMethod struct {
}
type SignupItem struct {
Name string `json:"name"`
Visible bool `json:"visible"`
Required bool `json:"required"`
Prompted bool `json:"prompted"`
Type string `json:"type"`
CustomCss string `json:"customCss"`
Label string `json:"label"`
Placeholder string `json:"placeholder"`
Options []string `json:"options"`
Regex string `json:"regex"`
Rule string `json:"rule"`
Name string `json:"name"`
Visible bool `json:"visible"`
Required bool `json:"required"`
Prompted bool `json:"prompted"`
CustomCss string `json:"customCss"`
Label string `json:"label"`
Placeholder string `json:"placeholder"`
Regex string `json:"regex"`
Rule string `json:"rule"`
}
type SigninItem struct {
@ -80,14 +78,13 @@ type Application struct {
EnableSamlCompress bool `json:"enableSamlCompress"`
EnableSamlC14n10 bool `json:"enableSamlC14n10"`
EnableSamlPostBinding bool `json:"enableSamlPostBinding"`
UseEmailAsSamlNameId bool `json:"useEmailAsSamlNameId"`
EnableWebAuthn bool `json:"enableWebAuthn"`
EnableLinkWithEmail bool `json:"enableLinkWithEmail"`
OrgChoiceMode string `json:"orgChoiceMode"`
SamlReplyUrl string `xorm:"varchar(100)" json:"samlReplyUrl"`
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
SigninMethods []*SigninMethod `xorm:"varchar(2000)" json:"signinMethods"`
SignupItems []*SignupItem `xorm:"varchar(3000)" json:"signupItems"`
SignupItems []*SignupItem `xorm:"varchar(2000)" json:"signupItems"`
SigninItems []*SigninItem `xorm:"mediumtext" json:"signinItems"`
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
@ -95,7 +92,6 @@ type Application struct {
Tags []string `xorm:"mediumtext" json:"tags"`
SamlAttributes []*SamlItem `xorm:"varchar(1000)" json:"samlAttributes"`
IsShared bool `json:"isShared"`
IpRestriction string `json:"ipRestriction"`
ClientId string `xorm:"varchar(100)" json:"clientId"`
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
@ -109,7 +105,6 @@ type Application struct {
SigninUrl string `xorm:"varchar(200)" json:"signinUrl"`
ForgetUrl string `xorm:"varchar(200)" json:"forgetUrl"`
AffiliationUrl string `xorm:"varchar(100)" json:"affiliationUrl"`
IpWhitelist string `xorm:"varchar(200)" json:"ipWhitelist"`
TermsOfUse string `xorm:"varchar(100)" json:"termsOfUse"`
SignupHtml string `xorm:"mediumtext" json:"signupHtml"`
SigninHtml string `xorm:"mediumtext" json:"signinHtml"`
@ -536,7 +531,7 @@ func GetMaskedApplication(application *Application, userId string) *Application
providerItems := []*ProviderItem{}
for _, providerItem := range application.Providers {
if providerItem.Provider != nil && (providerItem.Provider.Category == "OAuth" || providerItem.Provider.Category == "Web3" || providerItem.Provider.Category == "Captcha" || providerItem.Provider.Category == "SAML") {
if providerItem.Provider != nil && (providerItem.Provider.Category == "OAuth" || providerItem.Provider.Category == "Web3" || providerItem.Provider.Category == "Captcha") {
providerItems = append(providerItems, providerItem)
}
}
@ -723,15 +718,8 @@ func (application *Application) GetId() string {
}
func (application *Application) IsRedirectUriValid(redirectUri string) bool {
isValid, err := util.IsValidOrigin(redirectUri)
if err != nil {
panic(err)
}
if isValid {
return true
}
for _, targetUri := range application.RedirectUris {
redirectUris := append([]string{"http://localhost:", "https://localhost:", "http://127.0.0.1:", "http://casdoor-app", ".chromiumapp.org"}, application.RedirectUris...)
for _, targetUri := range redirectUris {
targetUriRegex := regexp.MustCompile(targetUri)
if targetUriRegex.MatchString(redirectUri) || strings.Contains(redirectUri, targetUri) {
return true

View File

@ -273,7 +273,7 @@ func CheckPasswordComplexity(user *User, password string) string {
return CheckPasswordComplexityByOrg(organization, password)
}
func CheckLdapUserPassword(user *User, password string, lang string) error {
func checkLdapUserPassword(user *User, password string, lang string) error {
ldaps, err := GetLdaps(user.Owner)
if err != nil {
return err
@ -368,7 +368,7 @@ func CheckUserPassword(organization string, username string, password string, la
}
// only for LDAP users
err = CheckLdapUserPassword(user, password, lang)
err = checkLdapUserPassword(user, password, lang)
if err != nil {
if err.Error() == "user not exist" {
return nil, fmt.Errorf(i18n.Translate(lang, "check:The user: %s doesn't exist in LDAP server"), username)
@ -381,13 +381,7 @@ func CheckUserPassword(organization string, username string, password string, la
if err != nil {
return nil, err
}
err = checkPasswordExpired(user, lang)
if err != nil {
return nil, err
}
}
return user, nil
}
@ -526,46 +520,11 @@ func CheckUsername(username string, lang string) string {
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 {
if oldUser.Name != user.Name {
organizationName := oldUser.Owner
if organizationName == "" {
organizationName = user.Owner
if msg := CheckUsername(user.Name, lang); msg != "" {
return msg
}
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) {
return i18n.Translate(lang, "check:Username already exists")
}
@ -580,11 +539,6 @@ func CheckUpdateUser(oldUser, user *User, lang string) string {
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 ""
}

View File

@ -1,104 +0,0 @@
// 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
}

View File

@ -1,53 +0,0 @@
// 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
}

View File

@ -16,18 +16,23 @@
package object
import "github.com/casdoor/casdoor/email"
import (
"crypto/tls"
// TestSmtpServer Test the SMTP server
func TestSmtpServer(provider *Provider) error {
smtpEmailProvider := email.NewSmtpEmailProvider(provider.ClientId, provider.ClientSecret, provider.Host, provider.Port, provider.Type, provider.DisableSsl)
sender, err := smtpEmailProvider.Dialer.Dial()
if err != nil {
return err
"github.com/casdoor/casdoor/email"
"github.com/casdoor/gomail/v2"
)
func getDialer(provider *Provider) *gomail.Dialer {
dialer := &gomail.Dialer{}
dialer = gomail.NewDialer(provider.Host, provider.Port, provider.ClientId, provider.ClientSecret)
if provider.Type == "SUBMAIL" {
dialer.TLSConfig = &tls.Config{InsecureSkipVerify: true}
}
defer sender.Close()
return nil
dialer.SSL = !provider.DisableSsl
return dialer
}
func SendEmail(provider *Provider, title string, content string, dest string, sender string) error {
@ -45,3 +50,16 @@ func SendEmail(provider *Provider, title string, content string, dest string, se
return emailProvider.Send(fromAddress, fromName, dest, title, content)
}
// DailSmtpServer Dail Smtp server
func DailSmtpServer(provider *Provider) error {
dialer := getDialer(provider)
sender, err := dialer.Dial()
if err != nil {
return err
}
defer sender.Close()
return nil
}

View File

@ -19,90 +19,124 @@ import (
"time"
)
type DashboardDateItem struct {
CreatedTime string `json:"createTime"`
type Dashboard struct {
OrganizationCounts []int `json:"organizationCounts"`
UserCounts []int `json:"userCounts"`
ProviderCounts []int `json:"providerCounts"`
ApplicationCounts []int `json:"applicationCounts"`
SubscriptionCounts []int `json:"subscriptionCounts"`
}
type DashboardMapItem struct {
dashboardDateItems []DashboardDateItem
itemCount int64
}
func GetDashboard(owner string) (*map[string][]int64, error) {
func GetDashboard(owner string) (*Dashboard, error) {
if owner == "All" {
owner = ""
}
dashboard := make(map[string][]int64)
dashboardMap := sync.Map{}
tableNames := []string{"organization", "user", "provider", "application", "subscription", "role", "group", "resource", "cert", "permission", "transaction", "model", "adapter", "enforcer"}
dashboard := &Dashboard{
OrganizationCounts: make([]int, 31),
UserCounts: make([]int, 31),
ProviderCounts: make([]int, 31),
ApplicationCounts: make([]int, 31),
SubscriptionCounts: make([]int, 31),
}
organizations := []Organization{}
users := []User{}
providers := []Provider{}
applications := []Application{}
subscriptions := []Subscription{}
time30day := time.Now().AddDate(0, 0, -30)
var wg sync.WaitGroup
var err error
wg.Add(len(tableNames))
ch := make(chan error, len(tableNames))
for _, tableName := range tableNames {
dashboard[tableName+"Counts"] = make([]int64, 31)
tableName := tableName
go func(ch chan error) {
defer wg.Done()
dashboardDateItems := []DashboardDateItem{}
var countResult int64
dbQueryBefore := ormer.Engine.Cols("created_time")
dbQueryAfter := ormer.Engine.Cols("created_time")
if owner != "" {
dbQueryAfter = dbQueryAfter.And("owner = ?", owner)
dbQueryBefore = dbQueryBefore.And("owner = ?", owner)
}
if countResult, err = dbQueryBefore.And("created_time < ?", time30day).Table(tableName).Count(); err != nil {
ch <- err
return
}
if err = dbQueryAfter.And("created_time >= ?", time30day).Table(tableName).Find(&dashboardDateItems); err != nil {
ch <- err
return
}
dashboardMap.Store(tableName, DashboardMapItem{
dashboardDateItems: dashboardDateItems,
itemCount: countResult,
})
}(ch)
}
wg.Wait()
close(ch)
for err = range ch {
if err != nil {
return nil, err
wg.Add(5)
go func() {
defer wg.Done()
if err := ormer.Engine.Find(&organizations, &Organization{Owner: owner}); err != nil {
panic(err)
}
}
}()
go func() {
defer wg.Done()
if err := ormer.Engine.Find(&users, &User{Owner: owner}); err != nil {
panic(err)
}
}()
go func() {
defer wg.Done()
if err := ormer.Engine.Find(&providers, &Provider{Owner: owner}); err != nil {
panic(err)
}
}()
go func() {
defer wg.Done()
if err := ormer.Engine.Find(&applications, &Application{Owner: owner}); err != nil {
panic(err)
}
}()
go func() {
defer wg.Done()
if err := ormer.Engine.Find(&subscriptions, &Subscription{Owner: owner}); err != nil {
panic(err)
}
}()
wg.Wait()
nowTime := time.Now()
for i := 30; i >= 0; i-- {
cutTime := nowTime.AddDate(0, 0, -i)
for _, tableName := range tableNames {
item, exist := dashboardMap.Load(tableName)
if !exist {
continue
}
dashboard[tableName+"Counts"][30-i] = countCreatedBefore(item.(DashboardMapItem), cutTime)
}
dashboard.OrganizationCounts[30-i] = countCreatedBefore(organizations, cutTime)
dashboard.UserCounts[30-i] = countCreatedBefore(users, cutTime)
dashboard.ProviderCounts[30-i] = countCreatedBefore(providers, cutTime)
dashboard.ApplicationCounts[30-i] = countCreatedBefore(applications, cutTime)
dashboard.SubscriptionCounts[30-i] = countCreatedBefore(subscriptions, cutTime)
}
return &dashboard, nil
return dashboard, nil
}
func countCreatedBefore(dashboardMapItem DashboardMapItem, before time.Time) int64 {
count := dashboardMapItem.itemCount
for _, e := range dashboardMapItem.dashboardDateItems {
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", e.CreatedTime)
if createdTime.Before(before) {
count++
func countCreatedBefore(objects interface{}, before time.Time) int {
count := 0
switch obj := objects.(type) {
case []Organization:
for _, o := range obj {
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", o.CreatedTime)
if createdTime.Before(before) {
count++
}
}
case []User:
for _, u := range obj {
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", u.CreatedTime)
if createdTime.Before(before) {
count++
}
}
case []Provider:
for _, p := range obj {
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", p.CreatedTime)
if createdTime.Before(before) {
count++
}
}
case []Application:
for _, a := range obj {
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", a.CreatedTime)
if createdTime.Before(before) {
count++
}
}
case []Subscription:
for _, s := range obj {
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", s.CreatedTime)
if createdTime.Before(before) {
count++
}
}
}
return count

View File

@ -48,16 +48,12 @@ type InitData struct {
Transactions []*Transaction `json:"transactions"`
}
var initDataNewOnly bool
func InitFromFile() {
initDataFile := conf.GetConfigString("initDataFile")
if initDataFile == "" {
return
}
initDataNewOnly = conf.GetConfigBool("initDataNewOnly")
initData, err := readInitDataFromFile(initDataFile)
if err != nil {
panic(err)
@ -186,9 +182,6 @@ func readInitDataFromFile(filePath string) (*InitData, error) {
if organization.Tags == nil {
organization.Tags = []string{}
}
if organization.AccountItems == nil {
organization.AccountItems = []*AccountItem{}
}
}
for _, application := range data.Applications {
if application.Providers == nil {
@ -273,9 +266,6 @@ func initDefinedOrganization(organization *Organization) {
}
if existed != nil {
if initDataNewOnly {
return
}
affected, err := deleteOrganization(organization)
if err != nil {
panic(err)
@ -285,9 +275,7 @@ func initDefinedOrganization(organization *Organization) {
}
}
organization.CreatedTime = util.GetCurrentTime()
if len(organization.AccountItems) == 0 {
organization.AccountItems = getBuiltInAccountItems()
}
organization.AccountItems = getBuiltInAccountItems()
_, err = AddOrganization(organization)
if err != nil {
@ -302,9 +290,6 @@ func initDefinedApplication(application *Application) {
}
if existed != nil {
if initDataNewOnly {
return
}
affected, err := deleteApplication(application)
if err != nil {
panic(err)
@ -326,9 +311,6 @@ func initDefinedUser(user *User) {
panic(err)
}
if existed != nil {
if initDataNewOnly {
return
}
affected, err := deleteUser(user)
if err != nil {
panic(err)
@ -355,9 +337,6 @@ func initDefinedCert(cert *Cert) {
}
if existed != nil {
if initDataNewOnly {
return
}
affected, err := DeleteCert(cert)
if err != nil {
panic(err)
@ -380,9 +359,6 @@ func initDefinedLdap(ldap *Ldap) {
}
if existed != nil {
if initDataNewOnly {
return
}
affected, err := DeleteLdap(ldap)
if err != nil {
panic(err)
@ -404,9 +380,6 @@ func initDefinedProvider(provider *Provider) {
}
if existed != nil {
if initDataNewOnly {
return
}
affected, err := DeleteProvider(provider)
if err != nil {
panic(err)
@ -428,9 +401,6 @@ func initDefinedModel(model *Model) {
}
if existed != nil {
if initDataNewOnly {
return
}
affected, err := DeleteModel(model)
if err != nil {
panic(err)
@ -453,9 +423,6 @@ func initDefinedPermission(permission *Permission) {
}
if existed != nil {
if initDataNewOnly {
return
}
affected, err := deletePermission(permission)
if err != nil {
panic(err)
@ -478,9 +445,6 @@ func initDefinedPayment(payment *Payment) {
}
if existed != nil {
if initDataNewOnly {
return
}
affected, err := DeletePayment(payment)
if err != nil {
panic(err)
@ -503,9 +467,6 @@ func initDefinedProduct(product *Product) {
}
if existed != nil {
if initDataNewOnly {
return
}
affected, err := DeleteProduct(product)
if err != nil {
panic(err)
@ -528,9 +489,6 @@ func initDefinedResource(resource *Resource) {
}
if existed != nil {
if initDataNewOnly {
return
}
affected, err := DeleteResource(resource)
if err != nil {
panic(err)
@ -553,9 +511,6 @@ func initDefinedRole(role *Role) {
}
if existed != nil {
if initDataNewOnly {
return
}
affected, err := deleteRole(role)
if err != nil {
panic(err)
@ -578,9 +533,6 @@ func initDefinedSyncer(syncer *Syncer) {
}
if existed != nil {
if initDataNewOnly {
return
}
affected, err := DeleteSyncer(syncer)
if err != nil {
panic(err)
@ -603,9 +555,6 @@ func initDefinedToken(token *Token) {
}
if existed != nil {
if initDataNewOnly {
return
}
affected, err := DeleteToken(token)
if err != nil {
panic(err)
@ -628,9 +577,6 @@ func initDefinedWebhook(webhook *Webhook) {
}
if existed != nil {
if initDataNewOnly {
return
}
affected, err := DeleteWebhook(webhook)
if err != nil {
panic(err)
@ -652,9 +598,6 @@ func initDefinedGroup(group *Group) {
panic(err)
}
if existed != nil {
if initDataNewOnly {
return
}
affected, err := deleteGroup(group)
if err != nil {
panic(err)
@ -676,9 +619,6 @@ func initDefinedAdapter(adapter *Adapter) {
panic(err)
}
if existed != nil {
if initDataNewOnly {
return
}
affected, err := DeleteAdapter(adapter)
if err != nil {
panic(err)
@ -700,9 +640,6 @@ func initDefinedEnforcer(enforcer *Enforcer) {
panic(err)
}
if existed != nil {
if initDataNewOnly {
return
}
affected, err := DeleteEnforcer(enforcer)
if err != nil {
panic(err)
@ -724,9 +661,6 @@ func initDefinedPlan(plan *Plan) {
panic(err)
}
if existed != nil {
if initDataNewOnly {
return
}
affected, err := DeletePlan(plan)
if err != nil {
panic(err)
@ -748,9 +682,6 @@ func initDefinedPricing(pricing *Pricing) {
panic(err)
}
if existed != nil {
if initDataNewOnly {
return
}
affected, err := DeletePricing(pricing)
if err != nil {
panic(err)
@ -772,9 +703,6 @@ func initDefinedInvitation(invitation *Invitation) {
panic(err)
}
if existed != nil {
if initDataNewOnly {
return
}
affected, err := DeleteInvitation(invitation)
if err != nil {
panic(err)
@ -810,9 +738,6 @@ func initDefinedSubscription(subscription *Subscription) {
panic(err)
}
if existed != nil {
if initDataNewOnly {
return
}
affected, err := DeleteSubscription(subscription)
if err != nil {
panic(err)
@ -834,9 +759,6 @@ func initDefinedTransaction(transaction *Transaction) {
panic(err)
}
if existed != nil {
if initDataNewOnly {
return
}
affected, err := DeleteTransaction(transaction)
if err != nil {
panic(err)

View File

@ -33,7 +33,6 @@ type Ldap struct {
Filter string `xorm:"varchar(200)" json:"filter"`
FilterFields []string `xorm:"varchar(100)" json:"filterFields"`
DefaultGroup string `xorm:"varchar(100)" json:"defaultGroup"`
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
AutoSync int `json:"autoSync"`
LastSync string `xorm:"varchar(100)" json:"lastSync"`
@ -150,7 +149,7 @@ func UpdateLdap(ldap *Ldap) (bool, error) {
}
affected, err := ormer.Engine.ID(ldap.Id).Cols("owner", "server_name", "host",
"port", "enable_ssl", "username", "password", "base_dn", "filter", "filter_fields", "auto_sync", "default_group", "password_type").Update(ldap)
"port", "enable_ssl", "username", "password", "base_dn", "filter", "filter_fields", "auto_sync", "default_group").Update(ldap)
if err != nil {
return false, nil
}

View File

@ -15,18 +15,14 @@
package object
import (
"crypto/md5"
"encoding/base64"
"errors"
"fmt"
"strings"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/i18n"
"github.com/casdoor/casdoor/util"
goldap "github.com/go-ldap/ldap/v3"
"github.com/thanhpk/randstr"
"golang.org/x/text/encoding/unicode"
)
type LdapConn struct {
@ -375,79 +371,6 @@ func GetExistUuids(owner string, uuids []string) ([]string, error) {
return existUuids, nil
}
func ResetLdapPassword(user *User, newPassword string, lang string) error {
ldaps, err := GetLdaps(user.Owner)
if err != nil {
return err
}
for _, ldapServer := range ldaps {
conn, err := ldapServer.GetLdapConn()
if err != nil {
continue
}
searchReq := goldap.NewSearchRequest(ldapServer.BaseDn, goldap.ScopeWholeSubtree, goldap.NeverDerefAliases,
0, 0, false, ldapServer.buildAuthFilterString(user), []string{}, nil)
searchResult, err := conn.Conn.Search(searchReq)
if err != nil {
conn.Close()
return err
}
if len(searchResult.Entries) == 0 {
conn.Close()
continue
}
if len(searchResult.Entries) > 1 {
conn.Close()
return fmt.Errorf(i18n.Translate(lang, "check:Multiple accounts with same uid, please check your ldap server"))
}
userDn := searchResult.Entries[0].DN
var pwdEncoded string
modifyPasswordRequest := goldap.NewModifyRequest(userDn, nil)
if conn.IsAD {
utf16 := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM)
pwdEncoded, err := utf16.NewEncoder().String("\"" + newPassword + "\"")
if err != nil {
conn.Close()
return err
}
modifyPasswordRequest.Replace("unicodePwd", []string{pwdEncoded})
modifyPasswordRequest.Replace("userAccountControl", []string{"512"})
} else {
switch ldapServer.PasswordType {
case "SSHA":
pwdEncoded, err = generateSSHA(newPassword)
break
case "MD5":
md5Byte := md5.Sum([]byte(newPassword))
md5Password := base64.StdEncoding.EncodeToString(md5Byte[:])
pwdEncoded = "{MD5}" + md5Password
break
case "Plain":
pwdEncoded = newPassword
break
default:
pwdEncoded = newPassword
break
}
modifyPasswordRequest.Replace("userPassword", []string{pwdEncoded})
}
err = conn.Conn.Modify(modifyPasswordRequest)
if err != nil {
conn.Close()
return err
}
conn.Close()
}
return nil
}
func (ldapUser *LdapUser) buildLdapUserName(owner string) (string, error) {
user := User{}
uidWithNumber := fmt.Sprintf("%s_%s", ldapUser.Uid, ldapUser.UidNumber)

View File

@ -1,36 +0,0 @@
// Copyright 2025 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"crypto/rand"
"crypto/sha1"
"encoding/base64"
)
func generateSSHA(password string) (string, error) {
salt := make([]byte, 4)
_, err := rand.Read(salt)
if err != nil {
return "", err
}
combined := append([]byte(password), salt...)
hash := sha1.Sum(combined)
hashWithSalt := append(hash[:], salt...)
encoded := base64.StdEncoding.EncodeToString(hashWithSalt)
return "{SSHA}" + encoded, nil
}

View File

@ -23,7 +23,7 @@ import (
func getNotificationClient(provider *Provider) (notify.Notifier, error) {
var client notify.Notifier
client, err := notification.GetNotificationProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.ClientId2, provider.ClientSecret2, provider.AppId, provider.Receiver, provider.Method, provider.Title, provider.Metadata, provider.RegionId)
client, err := notification.GetNotificationProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.ClientId2, provider.ClientSecret2, provider.AppId, provider.Receiver, provider.Method, provider.Title, provider.Metadata)
if err != nil {
return nil, err
}

View File

@ -44,18 +44,6 @@ type OidcDiscovery struct {
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 {
// Attempt to split the host and port, ignoring the error
hostWithoutPort, _, err := net.SplitHostPort(host)
@ -172,43 +160,3 @@ func GetJsonWebKeySet() (jose.JSONWebKeySet, error) {
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
}

View File

@ -56,37 +56,32 @@ type Organization struct {
WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"`
Logo string `xorm:"varchar(200)" json:"logo"`
LogoDark string `xorm:"varchar(200)" json:"logoDark"`
Favicon string `xorm:"varchar(200)" json:"favicon"`
Favicon string `xorm:"varchar(100)" json:"favicon"`
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
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"`
DefaultAvatar string `xorm:"varchar(200)" json:"defaultAvatar"`
DefaultApplication string `xorm:"varchar(100)" json:"defaultApplication"`
Tags []string `xorm:"mediumtext" json:"tags"`
Languages []string `xorm:"varchar(255)" json:"languages"`
ThemeData *ThemeData `xorm:"json" json:"themeData"`
MasterPassword string `xorm:"varchar(200)" json:"masterPassword"`
DefaultPassword string `xorm:"varchar(200)" json:"defaultPassword"`
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
DefaultPassword string `xorm:"varchar(100)" json:"defaultPassword"`
MasterVerificationCode string `xorm:"varchar(100)" json:"masterVerificationCode"`
IpWhitelist string `xorm:"varchar(200)" json:"ipWhitelist"`
InitScore int `json:"initScore"`
EnableSoftDeletion bool `json:"enableSoftDeletion"`
IsProfilePublic bool `json:"isProfilePublic"`
UseEmailAsUsername bool `json:"useEmailAsUsername"`
EnableTour bool `json:"enableTour"`
IpRestriction string `json:"ipRestriction"`
MfaItems []*MfaItem `xorm:"varchar(300)" json:"mfaItems"`
AccountItems []*AccountItem `xorm:"varchar(5000)" json:"accountItems"`
}
func GetOrganizationCount(owner, name, field, value string) (int64, error) {
func GetOrganizationCount(owner, field, value string) (int64, error) {
session := GetSession(owner, -1, -1, field, value, "", "")
return session.Count(&Organization{Name: name})
return session.Count(&Organization{})
}
func GetOrganizations(owner string, name ...string) ([]*Organization, error) {

View File

@ -364,7 +364,7 @@ func GetAllActions(userId string) ([]string, error) {
res := []string{}
for _, enforcer := range enforcers {
items := enforcer.GetAllActions()
items := enforcer.GetAllObjects()
res = append(res, items...)
}
return res, nil

View File

@ -16,7 +16,6 @@ package object
import (
"fmt"
"regexp"
"strings"
"github.com/beego/beego/context"
@ -71,7 +70,6 @@ type Provider struct {
IdP string `xorm:"mediumtext" json:"idP"`
IssuerUrl string `xorm:"varchar(100)" json:"issuerUrl"`
EnableSignAuthnRequest bool `json:"enableSignAuthnRequest"`
EmailRegex string `xorm:"varchar(200)" json:"emailRegex"`
ProviderUrl string `xorm:"varchar(200)" json:"providerUrl"`
}
@ -202,13 +200,6 @@ func UpdateProvider(id string, provider *Provider) (bool, error) {
return false, nil
}
if provider.EmailRegex != "" {
_, err := regexp.Compile(provider.EmailRegex)
if err != nil {
return false, err
}
}
if name != provider.Name {
err := providerChangeTrigger(name, provider.Name)
if err != nil {
@ -243,13 +234,6 @@ func AddProvider(provider *Provider) (bool, error) {
provider.IntranetEndpoint = util.GetEndPoint(provider.IntranetEndpoint)
}
if provider.EmailRegex != "" {
_, err := regexp.Compile(provider.EmailRegex)
if err != nil {
return false, err
}
}
affected, err := ormer.Engine.Insert(provider)
if err != nil {
return false, err
@ -437,7 +421,7 @@ func FromProviderToIdpInfo(ctx *context.Context, provider *Provider) *idp.Provid
providerInfo.ClientId = provider.ClientId2
providerInfo.ClientSecret = provider.ClientSecret2
}
} else if provider.Type == "ADFS" || provider.Type == "AzureAD" || provider.Type == "AzureADB2C" || provider.Type == "Casdoor" || provider.Type == "Okta" {
} else if provider.Type == "AzureAD" || provider.Type == "AzureADB2C" || provider.Type == "ADFS" || provider.Type == "Okta" {
providerInfo.HostUrl = provider.Domain
}

View File

@ -33,7 +33,7 @@ var (
func init() {
logPostOnly = conf.GetConfigBool("logPostOnly")
passwordRegex = regexp.MustCompile("\"password\":\"([^\"]*?)\"")
passwordRegex = regexp.MustCompile("\"password\":\".+\"")
}
type Record struct {
@ -50,7 +50,7 @@ func maskPassword(recordString string) string {
}
func NewRecord(ctx *context.Context) (*casvisorsdk.Record, error) {
clientIp := strings.Replace(util.GetClientIpFromRequest(ctx.Request), ": ", "", -1)
ip := strings.Replace(util.GetIPFromRequest(ctx.Request), ": ", "", -1)
action := strings.Replace(ctx.Request.URL.Path, "/api/", "", -1)
requestUri := util.FilterQuery(ctx.Request.RequestURI, []string{"accessToken"})
if len(requestUri) > 1000 {
@ -83,7 +83,7 @@ func NewRecord(ctx *context.Context) (*casvisorsdk.Record, error) {
record := casvisorsdk.Record{
Name: util.GenerateId(),
CreatedTime: util.GetCurrentTime(),
ClientIp: clientIp,
ClientIp: ip,
User: "",
Method: ctx.Request.Method,
RequestUri: requestUri,

View File

@ -338,10 +338,6 @@ func roleChangeTrigger(oldName string, newName string) error {
for _, role := range roles {
for j, u := range role.Roles {
if u == "*" {
continue
}
owner, name := util.GetOwnerAndNameFromId(u)
if name == oldName {
role.Roles[j] = util.GetId(owner, newName)
@ -362,10 +358,6 @@ func roleChangeTrigger(oldName string, newName string) error {
for _, permission := range permissions {
for j, u := range permission.Roles {
// u = organization/username
if u == "*" {
continue
}
owner, name := util.GetOwnerAndNameFromId(u)
if name == oldName {
permission.Roles[j] = util.GetId(owner, newName)

View File

@ -26,7 +26,6 @@ import (
"errors"
"fmt"
"io"
"strings"
"time"
"github.com/beevik/etree"
@ -66,11 +65,7 @@ func NewSamlResponse(application *Application, user *User, host string, certific
assertion.CreateAttr("IssueInstant", now)
assertion.CreateElement("saml:Issuer").SetText(host)
subject := assertion.CreateElement("saml:Subject")
nameIDValue := user.Name
if application.UseEmailAsSamlNameId {
nameIDValue = user.Email
}
subject.CreateElement("saml:NameID").SetText(nameIDValue)
subject.CreateElement("saml:NameID").SetText(user.Name)
subjectConfirmation := subject.CreateElement("saml:SubjectConfirmation")
subjectConfirmation.CreateAttr("Method", "urn:oasis:names:tc:SAML:2.0:cm:bearer")
subjectConfirmationData := subjectConfirmation.CreateElement("saml:SubjectConfirmationData")
@ -189,17 +184,17 @@ type NameIDFormat struct {
}
type SingleSignOnService struct {
// XMLName xml.Name
XMLName xml.Name
Binding string `xml:"Binding,attr"`
Location string `xml:"Location,attr"`
}
type Attribute struct {
// XMLName xml.Name
Xmlns string `xml:"xmlns,attr"`
Name string `xml:"Name,attr"`
NameFormat string `xml:"NameFormat,attr"`
FriendlyName string `xml:"FriendlyName,attr"`
Xmlns string `xml:"xmlns,attr"`
Values []string `xml:"AttributeValue"`
}
@ -223,13 +218,10 @@ func GetSamlMeta(application *Application, host string, enablePostBinding bool)
originFrontend, originBackend := getOriginFromHost(host)
idpLocation := ""
idpBinding := ""
if enablePostBinding {
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 {
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{
@ -262,7 +254,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"},
},
SingleSignOnService: SingleSignOnService{
Binding: idpBinding,
Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
Location: idpLocation,
},
ProtocolSupportEnumeration: "urn:oasis:names:tc:SAML:2.0:protocol",
@ -277,38 +269,29 @@ func GetSamlMeta(application *Application, host string, enablePostBinding bool)
func GetSamlResponse(application *Application, user *User, samlRequest string, host string) (string, string, string, error) {
// request type
method := "GET"
samlRequest = strings.ReplaceAll(samlRequest, " ", "+")
// base64 decode
defated, err := base64.StdEncoding.DecodeString(samlRequest)
if err != nil {
return "", "", "", fmt.Errorf("err: Failed to decode SAML request, %s", err.Error())
}
var requestByte []byte
// decompress
var buffer bytes.Buffer
rdr := flate.NewReader(bytes.NewReader(defated))
if strings.Contains(string(defated), "xmlns:") {
requestByte = defated
} else {
// decompress
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
for {
_, err = io.CopyN(&buffer, rdr, 1024)
if err != nil {
if err == io.EOF {
break
}
return "", "", "", err
}
requestByte = buffer.Bytes()
}
var authnRequest saml.AuthNRequest
err = xml.Unmarshal(requestByte, &authnRequest)
err = xml.Unmarshal(buffer.Bytes(), &authnRequest)
if err != nil {
return "", "", "", fmt.Errorf("err: Failed to unmarshal AuthnRequest, please check the SAML request, %s", err.Error())
}
@ -338,9 +321,6 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
} else if authnRequest.AssertionConsumerServiceURL == "" {
return "", "", "", fmt.Errorf("err: SAML request don't has attribute 'AssertionConsumerServiceURL' in <samlp:AuthnRequest>")
}
if authnRequest.ProtocolBinding == "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" {
method = "POST"
}
_, originBackend := getOriginFromHost(host)
@ -406,7 +386,7 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
}
// NewSamlResponse11 return a saml1.1 response(not 2.0)
func NewSamlResponse11(application *Application, user *User, requestID string, host string) (*etree.Element, error) {
func NewSamlResponse11(user *User, requestID string, host string) (*etree.Element, error) {
samlResponse := &etree.Element{
Space: "samlp",
Tag: "Response",
@ -450,11 +430,7 @@ func NewSamlResponse11(application *Application, user *User, requestID string, h
// nameIdentifier inside subject
nameIdentifier := subject.CreateElement("saml:NameIdentifier")
// nameIdentifier.CreateAttr("Format", "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress")
if application.UseEmailAsSamlNameId {
nameIdentifier.SetText(user.Email)
} else {
nameIdentifier.SetText(user.Name)
}
nameIdentifier.SetText(user.Name)
// subjectConfirmation inside subject
subjectConfirmation := subject.CreateElement("saml:SubjectConfirmation")
@ -463,11 +439,7 @@ func NewSamlResponse11(application *Application, user *User, requestID string, h
attributeStatement := assertion.CreateElement("saml:AttributeStatement")
subjectInAttribute := attributeStatement.CreateElement("saml:Subject")
nameIdentifierInAttribute := subjectInAttribute.CreateElement("saml:NameIdentifier")
if application.UseEmailAsSamlNameId {
nameIdentifierInAttribute.SetText(user.Email)
} else {
nameIdentifierInAttribute.SetText(user.Name)
}
nameIdentifierInAttribute.SetText(user.Name)
subjectConfirmationInAttribute := subjectInAttribute.CreateElement("saml:SubjectConfirmation")
subjectConfirmationInAttribute.CreateElement("saml:ConfirmationMethod").SetText("urn:oasis:names:tc:SAML:1.0:cm:artifact")

View File

@ -102,6 +102,14 @@ func GetTokenByAccessToken(accessToken string) (*Token, error) {
return nil, err
}
if !existed {
token = Token{AccessToken: accessToken}
existed, err = ormer.Engine.Get(&token)
if err != nil {
return nil, err
}
}
if !existed {
return nil, nil
}
@ -115,6 +123,14 @@ func GetTokenByRefreshToken(refreshToken string) (*Token, error) {
return nil, err
}
if !existed {
token = Token{RefreshToken: refreshToken}
existed, err = ormer.Engine.Get(&token)
if err != nil {
return nil, err
}
}
if !existed {
return nil, nil
}
@ -124,7 +140,6 @@ func GetTokenByRefreshToken(refreshToken string) (*Token, error) {
func GetTokenByTokenValue(tokenValue, tokenTypeHint string) (*Token, error) {
switch tokenTypeHint {
case "access_token":
case "access-token":
token, err := GetTokenByAccessToken(tokenValue)
if err != nil {
return nil, err
@ -133,7 +148,6 @@ func GetTokenByTokenValue(tokenValue, tokenTypeHint string) (*Token, error) {
return token, nil
}
case "refresh_token":
case "refresh-token":
token, err := GetTokenByRefreshToken(tokenValue)
if err != nil {
return nil, err

View File

@ -22,7 +22,6 @@ import (
"encoding/xml"
"fmt"
"math/rand"
"strings"
"sync"
"time"
@ -185,15 +184,6 @@ func StoreCasTokenForProxyTicket(token *CasAuthenticationSuccess, targetService,
return proxyTicket
}
func escapeXMLText(input string) (string, error) {
var sb strings.Builder
err := xml.EscapeText(&sb, []byte(input))
if err != nil {
return "", err
}
return sb.String(), nil
}
func GenerateCasToken(userId string, service string) (string, error) {
user, err := GetUser(userId)
if err != nil {
@ -235,11 +225,6 @@ func GenerateCasToken(userId string, service string) (string, error) {
}
if value != "" {
if escapedValue, err := escapeXMLText(value); err != nil {
return "", err
} else {
value = escapedValue
}
authenticationSuccess.Attributes.UserAttributes.Attributes = append(authenticationSuccess.Attributes.UserAttributes.Attributes, &CasNamedAttribute{
Name: k,
Value: value,
@ -296,7 +281,7 @@ func GetValidationBySaml(samlRequest string, host string) (string, string, error
return "", "", fmt.Errorf("the application for user %s is not found", userId)
}
samlResponse, err := NewSamlResponse11(application, user, request.RequestID, host)
samlResponse, err := NewSamlResponse11(user, request.RequestID, host)
if err != nil {
return "", "", err
}

View File

@ -309,29 +309,22 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
}, nil
}
var oldTokenScope string
if application.TokenFormat == "JWT-Standard" {
oldToken, err := ParseStandardJwtToken(refreshToken, cert)
_, err = ParseStandardJwtToken(refreshToken, cert)
if err != nil {
return &TokenError{
Error: InvalidGrant,
ErrorDescription: fmt.Sprintf("parse refresh token error: %s", err.Error()),
}, nil
}
oldTokenScope = oldToken.Scope
} else {
oldToken, err := ParseJwtToken(refreshToken, cert)
_, err = ParseJwtToken(refreshToken, cert)
if err != nil {
return &TokenError{
Error: InvalidGrant,
ErrorDescription: fmt.Sprintf("parse refresh token error: %s", err.Error()),
}, nil
}
oldTokenScope = oldToken.Scope
}
if scope == "" {
scope = oldTokenScope
}
// generate a new token
@ -339,9 +332,6 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
if err != nil {
return nil, err
}
if user == nil {
return "", fmt.Errorf("The user: %s doesn't exist", util.GetId(application.Organization, token.User))
}
if user.IsForbidden {
return &TokenError{
@ -511,7 +501,7 @@ func GetPasswordToken(application *Application, username string, password string
}
if user.Ldap != "" {
err = CheckLdapUserPassword(user, password, "en")
err = checkLdapUserPassword(user, password, "en")
} else {
err = CheckPassword(user, password, "en")
}

View File

@ -129,7 +129,6 @@ type User struct {
Bilibili string `xorm:"bilibili varchar(100)" json:"bilibili"`
Okta string `xorm:"okta varchar(100)" json:"okta"`
Douyin string `xorm:"douyin varchar(100)" json:"douyin"`
Kwai string `xorm:"kwai varchar(100)" json:"kwai"`
Line string `xorm:"line varchar(100)" json:"line"`
Amazon string `xorm:"amazon varchar(100)" json:"amazon"`
Auth0 string `xorm:"auth0 varchar(100)" json:"auth0"`
@ -201,14 +200,12 @@ type User struct {
Permissions []*Permission `json:"permissions"`
Groups []string `xorm:"groups varchar(1000)" json:"groups"`
LastChangePasswordTime string `xorm:"varchar(100)" json:"lastChangePasswordTime"`
LastSigninWrongTime string `xorm:"varchar(100)" json:"lastSigninWrongTime"`
SigninWrongTimes int `json:"signinWrongTimes"`
LastSigninWrongTime string `xorm:"varchar(100)" json:"lastSigninWrongTime"`
SigninWrongTimes int `json:"signinWrongTimes"`
ManagedAccounts []ManagedAccount `xorm:"managedAccounts blob" json:"managedAccounts"`
MfaAccounts []MfaAccount `xorm:"mfaAccounts blob" json:"mfaAccounts"`
NeedUpdatePassword bool `json:"needUpdatePassword"`
IpWhitelist string `xorm:"varchar(200)" json:"ipWhitelist"`
}
type Userinfo struct {
@ -238,7 +235,6 @@ type MfaAccount struct {
AccountName string `xorm:"varchar(100)" json:"accountName"`
Issuer string `xorm:"varchar(100)" json:"issuer"`
SecretKey string `xorm:"varchar(100)" json:"secretKey"`
Origin string `xorm:"varchar(100)" json:"origin"`
}
type FaceId struct {
@ -681,10 +677,6 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
user.Password = oldUser.Password
}
if user.Id != oldUser.Id && user.Id == "" {
user.Id = oldUser.Id
}
if user.Avatar != oldUser.Avatar && user.Avatar != "" && user.PermanentAvatar != "*" {
user.PermanentAvatar, err = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar, false)
if err != nil {
@ -697,14 +689,14 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
"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",
"is_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts", "face_ids", "mfaAccounts",
"signin_wrong_times", "last_change_password_time", "last_signin_wrong_time", "groups", "access_key", "access_secret", "mfa_phone_enabled", "mfa_email_enabled",
"signin_wrong_times", "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",
"baidu", "alipay", "casdoor", "infoflow", "apple", "azuread", "azureadb2c", "slack", "steam", "bilibili", "okta", "douyin", "kwai", "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",
"eveonline", "fitbit", "gitea", "heroku", "influxcloud", "instagram", "intercom", "kakao", "lastfm", "mailru", "meetup",
"microsoftonline", "naver", "nextcloud", "onedrive", "oura", "patreon", "paypal", "salesforce", "shopify", "soundcloud",
"spotify", "strava", "stripe", "type", "tiktok", "tumblr", "twitch", "twitter", "typetalk", "uber", "vk", "wepay", "xero", "yahoo",
"yammer", "yandex", "zoom", "custom", "need_update_password", "ip_whitelist",
"yammer", "yandex", "zoom", "custom", "need_update_password",
}
}
if isAdmin {
@ -823,10 +815,6 @@ func AddUser(user *User) (bool, error) {
user.UpdateUserPassword(organization)
}
if user.CreatedTime == "" {
user.CreatedTime = util.GetCurrentTime()
}
err = user.UpdateUserHash()
if err != nil {
return false, err
@ -846,14 +834,11 @@ func AddUser(user *User) (bool, error) {
}
}
rankingItem := GetAccountItemByName("Ranking", organization)
if rankingItem != nil {
count, err := GetUserCount(user.Owner, "", "", "")
if err != nil {
return false, err
}
user.Ranking = int(count + 1)
count, err := GetUserCount(user.Owner, "", "", "")
if err != nil {
return false, err
}
user.Ranking = int(count + 1)
if user.Groups != nil && len(user.Groups) > 0 {
_, err = userEnforcer.UpdateGroupsForUser(user.GetId(), user.Groups)
@ -965,17 +950,7 @@ func DeleteUser(user *User) (bool, error) {
return false, err
}
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)
}
return deleteUser(user)
}
func GetUserInfo(user *User, scope string, aud string, host string) (*Userinfo, error) {

View File

@ -271,213 +271,113 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
if oldUser.Owner != newUser.Owner {
item := GetAccountItemByName("Organization", organization)
if item == nil {
newUser.Owner = oldUser.Owner
} else {
itemsChanged = append(itemsChanged, item)
}
itemsChanged = append(itemsChanged, item)
}
if oldUser.Name != newUser.Name {
item := GetAccountItemByName("Name", organization)
if item == nil {
newUser.Name = oldUser.Name
} else {
itemsChanged = append(itemsChanged, item)
}
itemsChanged = append(itemsChanged, item)
}
if oldUser.Id != newUser.Id {
item := GetAccountItemByName("ID", organization)
if item == nil {
newUser.Id = oldUser.Id
} else {
itemsChanged = append(itemsChanged, item)
}
itemsChanged = append(itemsChanged, item)
}
if oldUser.DisplayName != newUser.DisplayName {
item := GetAccountItemByName("Display name", organization)
if item == nil {
newUser.DisplayName = oldUser.DisplayName
} else {
itemsChanged = append(itemsChanged, item)
}
itemsChanged = append(itemsChanged, item)
}
if oldUser.Avatar != newUser.Avatar {
item := GetAccountItemByName("Avatar", organization)
if item == nil {
newUser.Avatar = oldUser.Avatar
} else {
itemsChanged = append(itemsChanged, item)
}
itemsChanged = append(itemsChanged, item)
}
if oldUser.Type != newUser.Type {
item := GetAccountItemByName("User type", organization)
if item == nil {
newUser.Type = oldUser.Type
} else {
itemsChanged = append(itemsChanged, item)
}
itemsChanged = append(itemsChanged, item)
}
// The password is *** when not modified
if oldUser.Password != newUser.Password && newUser.Password != "***" {
item := GetAccountItemByName("Password", organization)
if item == nil {
newUser.Password = oldUser.Password
} else {
itemsChanged = append(itemsChanged, item)
}
itemsChanged = append(itemsChanged, item)
}
if oldUser.Email != newUser.Email {
item := GetAccountItemByName("Email", organization)
if item == nil {
newUser.Email = oldUser.Email
} else {
itemsChanged = append(itemsChanged, item)
}
itemsChanged = append(itemsChanged, item)
}
if oldUser.Phone != newUser.Phone {
item := GetAccountItemByName("Phone", organization)
if item == nil {
newUser.Phone = oldUser.Phone
} else {
itemsChanged = append(itemsChanged, item)
}
itemsChanged = append(itemsChanged, item)
}
if oldUser.CountryCode != newUser.CountryCode {
item := GetAccountItemByName("Country code", organization)
if item == nil {
newUser.CountryCode = oldUser.CountryCode
} else {
itemsChanged = append(itemsChanged, item)
}
itemsChanged = append(itemsChanged, item)
}
if oldUser.Region != newUser.Region {
item := GetAccountItemByName("Country/Region", organization)
if item == nil {
newUser.Region = oldUser.Region
} else {
itemsChanged = append(itemsChanged, item)
}
itemsChanged = append(itemsChanged, item)
}
if oldUser.Location != newUser.Location {
item := GetAccountItemByName("Location", organization)
if item == nil {
newUser.Location = oldUser.Location
} else {
itemsChanged = append(itemsChanged, item)
}
itemsChanged = append(itemsChanged, item)
}
if oldUser.Affiliation != newUser.Affiliation {
item := GetAccountItemByName("Affiliation", organization)
if item == nil {
newUser.Affiliation = oldUser.Affiliation
} else {
itemsChanged = append(itemsChanged, item)
}
itemsChanged = append(itemsChanged, item)
}
if oldUser.Title != newUser.Title {
item := GetAccountItemByName("Title", organization)
if item == nil {
newUser.Title = oldUser.Title
} else {
itemsChanged = append(itemsChanged, item)
}
itemsChanged = append(itemsChanged, item)
}
if oldUser.Homepage != newUser.Homepage {
item := GetAccountItemByName("Homepage", organization)
if item == nil {
newUser.Homepage = oldUser.Homepage
} else {
itemsChanged = append(itemsChanged, item)
}
itemsChanged = append(itemsChanged, item)
}
if oldUser.Bio != newUser.Bio {
item := GetAccountItemByName("Bio", organization)
if item == nil {
newUser.Bio = oldUser.Bio
} else {
itemsChanged = append(itemsChanged, item)
}
itemsChanged = append(itemsChanged, item)
}
if oldUser.Tag != newUser.Tag {
item := GetAccountItemByName("Tag", organization)
if item == nil {
newUser.Tag = oldUser.Tag
} else {
itemsChanged = append(itemsChanged, item)
}
itemsChanged = append(itemsChanged, item)
}
if oldUser.SignupApplication != newUser.SignupApplication {
item := GetAccountItemByName("Signup application", organization)
if item == nil {
newUser.SignupApplication = oldUser.SignupApplication
} else {
itemsChanged = append(itemsChanged, item)
}
itemsChanged = append(itemsChanged, item)
}
if oldUser.Gender != newUser.Gender {
item := GetAccountItemByName("Gender", organization)
if item == nil {
newUser.Gender = oldUser.Gender
} else {
itemsChanged = append(itemsChanged, item)
}
itemsChanged = append(itemsChanged, item)
}
if oldUser.Birthday != newUser.Birthday {
item := GetAccountItemByName("Birthday", organization)
if item == nil {
newUser.Birthday = oldUser.Birthday
} else {
itemsChanged = append(itemsChanged, item)
}
itemsChanged = append(itemsChanged, item)
}
if oldUser.Education != newUser.Education {
item := GetAccountItemByName("Education", organization)
if item == nil {
newUser.Education = oldUser.Education
} else {
itemsChanged = append(itemsChanged, item)
}
itemsChanged = append(itemsChanged, item)
}
if oldUser.IdCard != newUser.IdCard {
item := GetAccountItemByName("ID card", organization)
if item == nil {
newUser.IdCard = oldUser.IdCard
} else {
itemsChanged = append(itemsChanged, item)
}
itemsChanged = append(itemsChanged, item)
}
if oldUser.IdCardType != newUser.IdCardType {
item := GetAccountItemByName("ID card type", organization)
if item == nil {
newUser.IdCardType = oldUser.IdCardType
} else {
itemsChanged = append(itemsChanged, item)
}
itemsChanged = append(itemsChanged, item)
}
oldUserPropertiesJson, _ := json.Marshal(oldUser.Properties)
newUserPropertiesJson, _ := json.Marshal(newUser.Properties)
if string(oldUserPropertiesJson) != string(newUserPropertiesJson) {
item := GetAccountItemByName("Properties", organization)
if item == nil {
newUser.Properties = oldUser.Properties
} else {
itemsChanged = append(itemsChanged, item)
}
itemsChanged = append(itemsChanged, item)
}
if oldUser.PreferredMfaType != newUser.PreferredMfaType {
item := GetAccountItemByName("Multi-factor authentication", organization)
if item == nil {
newUser.PreferredMfaType = oldUser.PreferredMfaType
} else {
itemsChanged = append(itemsChanged, item)
}
itemsChanged = append(itemsChanged, item)
}
if oldUser.Groups == nil {
@ -490,11 +390,7 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
newUserGroupsJson, _ := json.Marshal(newUser.Groups)
if string(oldUserGroupsJson) != string(newUserGroupsJson) {
item := GetAccountItemByName("Groups", organization)
if item == nil {
newUser.Groups = oldUser.Groups
} else {
itemsChanged = append(itemsChanged, item)
}
itemsChanged = append(itemsChanged, item)
}
if oldUser.Address == nil {
@ -508,125 +404,65 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
newUserAddressJson, _ := json.Marshal(newUser.Address)
if string(oldUserAddressJson) != string(newUserAddressJson) {
item := GetAccountItemByName("Address", organization)
if item == nil {
newUser.Address = oldUser.Address
} else {
itemsChanged = append(itemsChanged, item)
}
itemsChanged = append(itemsChanged, item)
}
if newUser.FaceIds != nil {
item := GetAccountItemByName("Face ID", organization)
if item == nil {
newUser.FaceIds = oldUser.FaceIds
} else {
itemsChanged = append(itemsChanged, item)
}
itemsChanged = append(itemsChanged, item)
}
if oldUser.IsAdmin != newUser.IsAdmin {
item := GetAccountItemByName("Is admin", organization)
if item == nil {
newUser.IsAdmin = oldUser.IsAdmin
} else {
itemsChanged = append(itemsChanged, item)
}
itemsChanged = append(itemsChanged, item)
}
if oldUser.IsForbidden != newUser.IsForbidden {
item := GetAccountItemByName("Is forbidden", organization)
if item == nil {
newUser.IsForbidden = oldUser.IsForbidden
} else {
itemsChanged = append(itemsChanged, item)
}
itemsChanged = append(itemsChanged, item)
}
if oldUser.IsDeleted != newUser.IsDeleted {
item := GetAccountItemByName("Is deleted", organization)
if item == nil {
newUser.IsDeleted = oldUser.IsDeleted
} else {
itemsChanged = append(itemsChanged, item)
}
itemsChanged = append(itemsChanged, item)
}
if oldUser.NeedUpdatePassword != newUser.NeedUpdatePassword {
item := GetAccountItemByName("Need update password", organization)
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)
}
itemsChanged = append(itemsChanged, item)
}
if oldUser.Balance != newUser.Balance {
item := GetAccountItemByName("Balance", organization)
if item == nil {
newUser.Balance = oldUser.Balance
} else {
itemsChanged = append(itemsChanged, item)
}
itemsChanged = append(itemsChanged, item)
}
if oldUser.Score != newUser.Score {
item := GetAccountItemByName("Score", organization)
if item == nil {
newUser.Score = oldUser.Score
} else {
itemsChanged = append(itemsChanged, item)
}
itemsChanged = append(itemsChanged, item)
}
if oldUser.Karma != newUser.Karma {
item := GetAccountItemByName("Karma", organization)
if item == nil {
newUser.Karma = oldUser.Karma
} else {
itemsChanged = append(itemsChanged, item)
}
itemsChanged = append(itemsChanged, item)
}
if oldUser.Language != newUser.Language {
item := GetAccountItemByName("Language", organization)
if item == nil {
newUser.Language = oldUser.Language
} else {
itemsChanged = append(itemsChanged, item)
}
itemsChanged = append(itemsChanged, item)
}
if oldUser.Ranking != newUser.Ranking {
item := GetAccountItemByName("Ranking", organization)
if item == nil {
newUser.Ranking = oldUser.Ranking
} else {
itemsChanged = append(itemsChanged, item)
}
itemsChanged = append(itemsChanged, item)
}
if oldUser.Currency != newUser.Currency {
item := GetAccountItemByName("Currency", organization)
if item == nil {
newUser.Currency = oldUser.Currency
} else {
itemsChanged = append(itemsChanged, item)
}
itemsChanged = append(itemsChanged, item)
}
if oldUser.Hash != newUser.Hash {
item := GetAccountItemByName("Hash", organization)
if item == nil {
newUser.Hash = oldUser.Hash
} else {
itemsChanged = append(itemsChanged, item)
}
itemsChanged = append(itemsChanged, item)
}
for _, accountItem := range itemsChanged {

View File

@ -57,7 +57,7 @@ type VerificationRecord struct {
Receiver string `xorm:"varchar(100) index notnull" json:"receiver"`
Code string `xorm:"varchar(10) notnull" json:"code"`
Time int64 `xorm:"notnull" json:"time"`
IsUsed bool `xorm:"notnull" json:"isUsed"`
IsUsed bool
}
func IsAllowSend(user *User, remoteAddr, recordType string) error {
@ -166,76 +166,19 @@ func AddToVerificationRecord(user *User, provider *Provider, remoteAddr, recordT
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) {
record := &VerificationRecord{}
var record VerificationRecord
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 {
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 {
return nil, nil
}
return record, nil
return &record, nil
}
func CheckVerificationCode(dest string, code string, lang string) (*VerifyResult, error) {
@ -244,9 +187,7 @@ func CheckVerificationCode(dest string, code string, lang string) (*VerifyResult
return nil, err
}
if record == 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
return &VerifyResult{noRecordError, i18n.Translate(lang, "verification:The verification code has not been sent yet, or has already been used!")}, nil
}
timeoutInMinutes, err := conf.GetConfigInt64("verificationCodeTimeout")
@ -255,6 +196,9 @@ func CheckVerificationCode(dest string, code string, lang string) (*VerifyResult
}
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 {
return &VerifyResult{timeoutError, fmt.Sprintf(i18n.Translate(lang, "verification:You should verify your code in %d min!"), timeoutInMinutes)}, nil
}
@ -267,7 +211,7 @@ func CheckVerificationCode(dest string, code string, lang string) (*VerifyResult
}
func DisableVerificationCode(dest string) error {
record, err := getUnusedVerificationRecord(dest)
record, err := getVerificationRecord(dest)
if record == nil || err != nil {
return nil
}

View File

@ -68,10 +68,8 @@ func handleAccessRequest(w radius.ResponseWriter, r *radius.Request) {
log.Printf("handleAccessRequest() username=%v, org=%v, password=%v", username, organization, password)
if organization == "" {
organization = conf.GetConfigString("radiusDefaultOrganization")
if organization == "" {
organization = "built-in"
}
w.Write(r.Response(radius.CodeAccessReject))
return
}
var user *object.User

View File

@ -16,11 +16,11 @@ package routers
import (
"net/http"
"strings"
"github.com/beego/beego/context"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
const (
@ -48,17 +48,7 @@ func CorsFilter(ctx *context.Context) {
originHostname := getHostname(origin)
host := removePort(ctx.Request.Host)
if origin == "null" {
origin = ""
}
isValid, err := util.IsValidOrigin(origin)
if err != nil {
ctx.ResponseWriter.WriteHeader(http.StatusForbidden)
responseError(ctx, err.Error())
return
}
if isValid {
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") {
setCorsHeaders(ctx, origin)
return
}

View File

@ -174,8 +174,6 @@ func initAPI() {
beego.Router("/api/get-all-actions", &controllers.ApiController{}, "GET:GetAllActions")
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-session", &controllers.ApiController{}, "GET:GetSingleSession")
beego.Router("/api/update-session", &controllers.ApiController{}, "POST:UpdateSession")
@ -292,7 +290,6 @@ func initAPI() {
beego.Router("/.well-known/openid-configuration", &controllers.RootController{}, "GET:GetOidcDiscovery")
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/proxyValidate", &controllers.RootController{}, "GET:CasProxyValidate")

View File

@ -43,10 +43,6 @@ func getWebBuildFolder() string {
return path
}
if util.FileExist(filepath.Join(frontendBaseDir, "index.html")) {
return frontendBaseDir
}
path = filepath.Join(frontendBaseDir, "web/build")
return path
}
@ -133,11 +129,6 @@ func StaticFilter(ctx *context.Context) {
path += urlPath
}
err := appendThemeCookie(ctx, urlPath)
if err != nil {
fmt.Println(err)
}
if strings.Contains(path, "/../") || !util.FileExist(path) {
path = webBuildFolder + "/index.html"
}

View File

@ -1,101 +0,0 @@
// Copyright 2025 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package routers
import (
"encoding/json"
"fmt"
"strings"
"github.com/beego/beego/context"
"github.com/casdoor/casdoor/object"
)
func appendThemeCookie(ctx *context.Context, urlPath string) error {
if urlPath == "/login" {
application, err := object.GetDefaultApplication(fmt.Sprintf("admin/built-in"))
if err != nil {
return err
}
if application.ThemeData != nil {
return setThemeDataCookie(ctx, application.ThemeData, application.Logo, application.FooterHtml)
}
organization := application.OrganizationObj
if organization == nil {
organization, err = object.GetOrganization(fmt.Sprintf("admin/built-in"))
if err != nil {
return err
}
}
if organization != nil {
return setThemeDataCookie(ctx, organization.ThemeData, organization.Logo, application.FooterHtml)
}
} else if strings.HasPrefix(urlPath, "/login/oauth/authorize") {
clientId := ctx.Input.Query("client_id")
if clientId == "" {
return nil
}
application, err := object.GetApplicationByClientId(clientId)
if err != nil {
return err
}
if application != nil {
organization, err := object.GetOrganization(fmt.Sprintf("admin/%s", application.Organization))
if err != nil {
return err
}
if application.ThemeData != nil {
return setThemeDataCookie(ctx, application.ThemeData, application.Logo, application.FooterHtml)
}
if organization != nil {
return setThemeDataCookie(ctx, organization.ThemeData, organization.Logo, application.FooterHtml)
}
}
} else if strings.HasPrefix(urlPath, "/login/") {
owner := strings.Replace(urlPath, "/login/", "", -1)
if owner != "undefined" && owner != "oauth/undefined" {
application, err := object.GetDefaultApplication(fmt.Sprintf("admin/%s", owner))
if err != nil {
return err
}
if application.ThemeData != nil {
return setThemeDataCookie(ctx, application.ThemeData, application.Logo, application.FooterHtml)
}
organization := application.OrganizationObj
if organization == nil {
organization, err = object.GetOrganization(fmt.Sprintf("admin/%s", owner))
if err != nil {
return err
}
}
if organization != nil {
return setThemeDataCookie(ctx, organization.ThemeData, organization.Logo, application.FooterHtml)
}
}
}
return nil
}
func setThemeDataCookie(ctx *context.Context, themeData *object.ThemeData, logoUrl string, footerHtml string) error {
themeDataString, err := json.Marshal(themeData)
if err != nil {
return err
}
ctx.SetCookie("organizationTheme", string(themeDataString))
ctx.SetCookie("organizationLogo", logoUrl)
ctx.SetCookie("organizationFootHtml", footerHtml)
return nil
}

View File

@ -1,64 +0,0 @@
// 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)
}
}

View File

@ -1,21 +0,0 @@
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
}

View File

@ -23,10 +23,7 @@ func GetStorageProvider(providerType string, clientId string, clientSecret strin
case "AWS S3":
return NewAwsS3StorageProvider(clientId, clientSecret, region, bucket, endpoint), nil
case "MinIO":
if region == "" {
region = "_"
}
return NewMinIOS3StorageProvider(clientId, clientSecret, region, bucket, endpoint), nil
return NewMinIOS3StorageProvider(clientId, clientSecret, "_", bucket, endpoint), nil
case "Aliyun OSS":
return NewAliyunOssStorageProvider(clientId, clientSecret, region, bucket, endpoint), nil
case "Tencent Cloud COS":
@ -41,8 +38,6 @@ func GetStorageProvider(providerType string, clientId string, clientSecret strin
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

View File

@ -7558,9 +7558,6 @@
"type": "integer",
"format": "int64"
},
"kwai": {
"type": "string"
},
"language": {
"type": "string"
},

View File

@ -4981,8 +4981,6 @@ definitions:
karma:
type: integer
format: int64
kwai:
type: string
language:
type: string
lark:

View File

@ -23,50 +23,50 @@ import (
"github.com/beego/beego/logs"
)
func getIpInfo(clientIp string) string {
if clientIp == "" {
func GetIPInfo(clientIP string) string {
if clientIP == "" {
return ""
}
ips := strings.Split(clientIp, ",")
res := strings.TrimSpace(ips[0])
//res := ""
//for i := range ips {
// ip := strings.TrimSpace(ips[i])
// ipstr := fmt.Sprintf("%s: %s", ip, "")
// if i != len(ips)-1 {
// res += ipstr + " -> "
// } else {
// res += ipstr
// }
//}
ips := strings.Split(clientIP, ",")
res := ""
for i := range ips {
ip := strings.TrimSpace(ips[i])
// desc := GetDescFromIP(ip)
ipstr := fmt.Sprintf("%s: %s", ip, "")
if i != len(ips)-1 {
res += ipstr + " -> "
} else {
res += ipstr
}
}
return res
}
func GetClientIpFromRequest(req *http.Request) string {
clientIp := req.Header.Get("x-forwarded-for")
if clientIp == "" {
func GetIPFromRequest(req *http.Request) string {
clientIP := req.Header.Get("x-forwarded-for")
if clientIP == "" {
ipPort := strings.Split(req.RemoteAddr, ":")
if len(ipPort) >= 1 && len(ipPort) <= 2 {
clientIp = ipPort[0]
clientIP = ipPort[0]
} else if len(ipPort) > 2 {
idx := strings.LastIndex(req.RemoteAddr, ":")
clientIp = req.RemoteAddr[0:idx]
clientIp = strings.TrimLeft(clientIp, "[")
clientIp = strings.TrimRight(clientIp, "]")
clientIP = req.RemoteAddr[0:idx]
clientIP = strings.TrimLeft(clientIP, "[")
clientIP = strings.TrimRight(clientIP, "]")
}
}
return getIpInfo(clientIp)
return GetIPInfo(clientIP)
}
func LogInfo(ctx *context.Context, f string, v ...interface{}) {
ipString := fmt.Sprintf("(%s) ", GetClientIpFromRequest(ctx.Request))
ipString := fmt.Sprintf("(%s) ", GetIPFromRequest(ctx.Request))
logs.Info(ipString+f, v...)
}
func LogWarning(ctx *context.Context, f string, v ...interface{}) {
ipString := fmt.Sprintf("(%s) ", GetClientIpFromRequest(ctx.Request))
ipString := fmt.Sprintf("(%s) ", GetIPFromRequest(ctx.Request))
logs.Warning(ipString+f, v...)
}

View File

@ -1,76 +0,0 @@
// 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)
}
}

View File

@ -1,97 +0,0 @@
// Copyright 2025 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package util
import (
"fmt"
"os"
"os/exec"
"runtime"
"strconv"
"strings"
)
func getPidByPort(port int) (int, error) {
var cmd *exec.Cmd
switch runtime.GOOS {
case "windows":
cmd = exec.Command("cmd", "/c", "netstat -ano | findstr :"+strconv.Itoa(port))
case "darwin", "linux":
cmd = exec.Command("lsof", "-t", "-i", ":"+strconv.Itoa(port))
default:
return 0, fmt.Errorf("unsupported OS: %s", runtime.GOOS)
}
output, err := cmd.Output()
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
if exitErr.ExitCode() == 1 {
return 0, nil
}
} else {
return 0, err
}
}
lines := strings.Split(string(output), "\n")
for _, line := range lines {
fields := strings.Fields(line)
if len(fields) > 0 {
if runtime.GOOS == "windows" {
if fields[1] == "0.0.0.0:"+strconv.Itoa(port) {
pid, err := strconv.Atoi(fields[len(fields)-1])
if err != nil {
return 0, err
}
return pid, nil
}
} else {
pid, err := strconv.Atoi(fields[0])
if err != nil {
return 0, err
}
return pid, nil
}
}
}
return 0, nil
}
func StopOldInstance(port int) error {
pid, err := getPidByPort(port)
if err != nil {
return err
}
if pid == 0 {
return nil
}
process, err := os.FindProcess(pid)
if err != nil {
return err
}
err = process.Kill()
if err != nil {
return err
} else {
fmt.Printf("The old instance with pid: %d has been stopped\n", pid)
}
return nil
}

View File

@ -17,7 +17,6 @@ package util
import (
"fmt"
"net/mail"
"net/url"
"regexp"
"strings"
@ -25,11 +24,10 @@ import (
)
var (
rePhone *regexp.Regexp
ReWhiteSpace *regexp.Regexp
ReFieldWhiteList *regexp.Regexp
ReUserName *regexp.Regexp
ReUserNameWithEmail *regexp.Regexp
rePhone *regexp.Regexp
ReWhiteSpace *regexp.Regexp
ReFieldWhiteList *regexp.Regexp
ReUserName *regexp.Regexp
)
func init() {
@ -37,7 +35,6 @@ func init() {
ReWhiteSpace, _ = regexp.Compile(`\s`)
ReFieldWhiteList, _ = regexp.Compile(`^[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 {
@ -54,9 +51,6 @@ func IsPhoneValid(phone string, countryCode string) bool {
}
func IsPhoneAllowInRegin(countryCode string, allowRegions []string) bool {
if ContainsString(allowRegions, "All") {
return true
}
return ContainsString(allowRegions, countryCode)
}
@ -103,21 +97,3 @@ func GetCountryCode(prefix string, phone string) (string, error) {
func FilterField(field string) bool {
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
}

View File

@ -1,5 +1,4 @@
const CracoLessPlugin = require("craco-less");
const path = require("path");
module.exports = {
devServer: {
@ -56,42 +55,47 @@ module.exports = {
},
],
webpack: {
configure: (webpackConfig, { env, paths }) => {
paths.appBuild = path.resolve(__dirname, "build-temp");
webpackConfig.output.path = path.resolve(__dirname, "build-temp");
// ignore webpack warnings by source-map-loader
configure: {
// ignore webpack warnings by source-map-loader
// https://github.com/facebook/create-react-app/pull/11752#issuecomment-1345231546
webpackConfig.ignoreWarnings = [
ignoreWarnings: [
function ignoreSourcemapsloaderWarnings(warning) {
return (
warning.module &&
warning.module.resource.includes("node_modules") &&
warning.module.resource.includes('node_modules') &&
warning.details &&
warning.details.includes("source-map-loader")
);
warning.details.includes('source-map-loader')
)
},
];
],
// use polyfill Buffer with Webpack 5
// https://viglucci.io/articles/how-to-polyfill-buffer-with-webpack-5
// https://craco.js.org/docs/configuration/webpack/
webpackConfig.resolve.fallback = {
buffer: require.resolve("buffer/"),
process: false,
util: false,
url: false,
zlib: false,
stream: false,
http: false,
https: false,
assert: false,
crypto: false,
os: false,
fs: false,
};
return webpackConfig;
resolve: {
fallback: {
// "process": require.resolve('process/browser'),
// "util": require.resolve("util/"),
// "url": require.resolve("url/"),
// "zlib": require.resolve("browserify-zlib"),
// "stream": require.resolve("stream-browserify"),
// "http": require.resolve("stream-http"),
// "https": require.resolve("https-browserify"),
// "assert": require.resolve("assert/"),
"buffer": require.resolve('buffer/'),
"process": false,
"util": false,
"url": false,
"zlib": false,
"stream": false,
"http": false,
"https": false,
"assert": false,
"buffer": false,
"crypto": false,
"os": false,
"fs": false,
},
}
},
},
}
};

View File

@ -1,21 +0,0 @@
const fs = require("fs");
const path = require("path");
const sourceDir = path.join(__dirname, "build-temp");
const targetDir = path.join(__dirname, "build");
if (!fs.existsSync(sourceDir)) {
// eslint-disable-next-line no-console
console.error(`Source directory "${sourceDir}" does not exist.`);
process.exit(1);
}
if (fs.existsSync(targetDir)) {
fs.rmSync(targetDir, {recursive: true, force: true});
// eslint-disable-next-line no-console
console.log(`Target directory "${targetDir}" has been deleted successfully.`);
}
fs.renameSync(sourceDir, targetDir);
// eslint-disable-next-line no-console
console.log(`Renamed "${sourceDir}" to "${targetDir}" successfully.`);

View File

@ -27,7 +27,6 @@
"copy-to-clipboard": "^3.3.1",
"core-js": "^3.25.0",
"craco-less": "^2.0.0",
"crypto-js": "^4.2.0",
"echarts": "^5.4.3",
"ethers": "5.6.9",
"face-api.js": "^0.22.2",
@ -57,7 +56,6 @@
"scripts": {
"start": "cross-env PORT=7001 craco start",
"build": "craco build",
"postbuild": "node mv.js",
"test": "craco test",
"eject": "craco eject",
"crowdin:sync": "crowdin upload && crowdin download",

View File

@ -36,7 +36,6 @@ const {Footer, Content} = Layout;
import {setTwoToneColor} from "@ant-design/icons";
import * as ApplicationBackend from "./backend/ApplicationBackend";
import * as Cookie from "cookie";
setTwoToneColor("rgb(87,52,211)");
@ -270,9 +269,7 @@ class App extends Component {
});
}
renderFooter(logo, footerHtml) {
logo = logo ?? this.state.logo;
footerHtml = footerHtml ?? this.state.application?.footerHtml;
renderFooter() {
return (
<React.Fragment>
{!this.state.account ? null : <div style={{display: "none"}} id="CasdoorApplicationName" value={this.state.account.signupApplication} />}
@ -283,14 +280,14 @@ class App extends Component {
}
}>
{
footerHtml && footerHtml !== "" ?
this.state.application?.footerHtml && this.state.application.footerHtml !== "" ?
<React.Fragment>
<div dangerouslySetInnerHTML={{__html: footerHtml}} />
<div dangerouslySetInnerHTML={{__html: this.state.application.footerHtml}} />
</React.Fragment>
: (
Conf.CustomFooter !== null ? Conf.CustomFooter : (
<React.Fragment>
Powered by <a target="_blank" href="https://casdoor.org" rel="noreferrer"><img style={{paddingBottom: "3px"}} height={"20px"} alt={"Casdoor"} src={logo} /></a>
Powered by <a target="_blank" href="https://casdoor.org" rel="noreferrer"><img style={{paddingBottom: "3px"}} height={"20px"} alt={"Casdoor"} src={this.state.logo} /></a>
</React.Fragment>
)
)
@ -311,7 +308,7 @@ class App extends Component {
AI Assistant
</a>
</Tooltip>
<a className="custom-link" style={{float: "right", marginTop: "2px"}} target="_blank" rel="noreferrer" href={`${Conf.AiAssistantUrl}`}>
<a className="custom-link" style={{float: "right", marginTop: "2px"}} target="_blank" rel="noreferrer" href={"https://ai.casbin.com"}>
<ShareAltOutlined className="custom-link" style={{fontSize: "20px", color: "rgb(140,140,140)"}} />
</a>
<a className="custom-link" style={{float: "right", marginRight: "30px", marginTop: "2px"}} target="_blank" rel="noreferrer" href={"https://github.com/casibase/casibase"}>
@ -329,7 +326,7 @@ class App extends Component {
}}
visible={this.state.isAiAssistantOpen}
>
<iframe id="iframeHelper" title={"iframeHelper"} src={`${Conf.AiAssistantUrl}/?isRaw=1`} width="100%" height="100%" scrolling="no" frameBorder="no" />
<iframe id="iframeHelper" title={"iframeHelper"} src={"https://ai.casbin.com/?isRaw=1"} width="100%" height="100%" scrolling="no" frameBorder="no" />
</Drawer>
);
}
@ -363,29 +360,9 @@ class App extends Component {
renderPage() {
if (this.isDoorPages()) {
let themeData = this.state.themeData;
let logo = this.state.logo;
let footerHtml = null;
if (this.state.organization === undefined) {
const curCookie = Cookie.parse(document.cookie);
if (curCookie["organizationTheme"] && curCookie["organizationTheme"] !== "null") {
themeData = JSON.parse(curCookie["organizationTheme"]);
}
if (curCookie["organizationLogo"] && curCookie["organizationLogo"] !== "") {
logo = curCookie["organizationLogo"];
}
if (curCookie["organizationFootHtml"] && curCookie["organizationFootHtml"] !== "") {
footerHtml = curCookie["organizationFootHtml"];
}
}
return (
<ConfigProvider theme={{
token: {
colorPrimary: themeData.colorPrimary,
borderRadius: themeData.borderRadius,
},
algorithm: Setting.getAlgorithm(this.state.themeAlgorithm),
algorithm: Setting.getAlgorithm(["default"]),
}}>
<StyleProvider hashPriority="high" transformers={[legacyLogicalPropertiesTransformer]}>
<Layout id="parent-area">
@ -395,7 +372,6 @@ class App extends Component {
<EntryPage
account={this.state.account}
theme={this.state.themeData}
themeAlgorithm={this.state.themeAlgorithm}
updateApplication={(application) => {
this.setState({
application: application,
@ -420,7 +396,7 @@ class App extends Component {
}
</Content>
{
this.renderFooter(logo, footerHtml)
this.renderFooter()
}
{
this.renderAiAssistant()
@ -470,6 +446,7 @@ class App extends Component {
setLogoutState={() => {
this.setState({
account: null,
themeAlgorithm: ["default"],
});
}}
/>

View File

@ -129,15 +129,6 @@ img {
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 {
border-bottom: none !important;
}

View File

@ -46,18 +46,12 @@ require("codemirror/mode/css/css");
const {Option} = Select;
const template = `<style>
.login-panel {
.login-panel{
padding: 40px 70px 0 70px;
border-radius: 10px;
background-color: #ffffff;
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>`;
const previewGrid = Setting.isMobile() ? 22 : 11;
@ -598,16 +592,6 @@ class ApplicationEditPage extends React.Component {
}} />
</Col>
</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"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("signup:Terms of Use"), i18next.t("signup:Terms of Use - Tooltip"))} :
@ -719,16 +703,6 @@ class ApplicationEditPage extends React.Component {
}} />
</Col>
</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"}} >
<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"))} :
@ -765,7 +739,7 @@ class ApplicationEditPage extends React.Component {
/>
<br />
<Button style={{marginBottom: "10px"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => {
copy(`${window.location.origin}/api/saml/metadata?application=admin/${encodeURIComponent(this.state.applicationName)}&enablePostBinding=${this.state.application.enableSamlPostBinding}`);
copy(`${window.location.origin}/api/saml/metadata?application=admin/${encodeURIComponent(this.state.applicationName)}&post=${this.state.application.enableSamlPostBinding}`);
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
}}
>
@ -993,7 +967,6 @@ class ApplicationEditPage extends React.Component {
<SigninTable
title={i18next.t("application:Signin items")}
table={this.state.application.signinItems}
themeAlgorithm={this.state.themeAlgorithm}
onUpdateTable={(value) => {
this.updateApplicationField("signinItems", value);
}}

View File

@ -1,98 +0,0 @@
// 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";
import i18next from "i18next";
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={i18next.t("model:Basic Editor")} key="basic" />
<TabPane tab={i18next.t("model: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;

View File

@ -31,6 +31,3 @@ export const ThemeDefault = {
};
export const CustomFooter = null;
// Blank or null to hide Ai Assistant button
export const AiAssistantUrl = "https://ai.casbin.com";

View File

@ -34,7 +34,6 @@ import PaymentResultPage from "./PaymentResultPage";
import QrCodePage from "./QrCodePage";
import CaptchaPage from "./CaptchaPage";
import CustomHead from "./basic/CustomHead";
import * as Util from "./auth/Util";
class EntryPage extends React.Component {
constructor(props) {
@ -95,23 +94,13 @@ 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 (
<React.Fragment>
<CustomHead headerHtml={this.state.application?.headerHtml} />
<div className={`${isDarkMode ? "loginBackgroundDark" : "loginBackground"}`}
<div className="loginBackground"
style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${this.state.application?.formBackgroundUrl})`}}>
<Spin size="large" spinning={this.state.application === undefined && this.state.pricing === undefined} tip={i18next.t("login:Loading")}
style={{width: "100%", margin: "0 auto", position: "absolute"}} />
style={{margin: "0 auto"}} />
<Switch>
<Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} applicationName={authConfig.appName} onUpdateApplication={onUpdateApplication} {...props} />)} />
<Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
@ -135,7 +124,6 @@ class EntryPage extends React.Component {
<Route exact path="/captcha" render={(props) => <CaptchaPage {...props} />} />
</Switch>
</div>
</React.Fragment>
);
}

View File

@ -1,74 +0,0 @@
// 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);
const currentLang = localStorage.getItem("language") || "en";
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);
if (initialModelText && iframeRef.current?.contentWindow) {
iframeRef.current.contentWindow.postMessage({
type: "initializeModel",
modelText: initialModelText,
lang: currentLang,
}, "*");
}
}
};
window.addEventListener("message", handleMessage);
return () => window.removeEventListener("message", handleMessage);
}, [onModelTextChange, initialModelText, currentLang]);
useImperativeHandle(ref, () => ({
getModelText: () => {
if (iframeRef.current?.contentWindow) {
iframeRef.current.contentWindow.postMessage({
type: "getModelText",
}, "*");
}
},
updateModelText: (newModelText) => {
if (iframeReady && iframeRef.current?.contentWindow) {
iframeRef.current.contentWindow.postMessage({
type: "updateModelText",
modelText: newModelText,
}, "*");
}
},
}));
return (
<iframe
ref={iframeRef}
src={`https://editor.casbin.org/model-editor?lang=${currentLang}`}
frameBorder="0"
width="100%"
height="500px"
title="Casbin Model Editor"
/>
);
});
export default IframeEditor;

View File

@ -20,7 +20,6 @@ import * as ApplicationBackend from "./backend/ApplicationBackend";
import * as Setting from "./Setting";
import i18next from "i18next";
import copy from "copy-to-clipboard";
import * as GroupBackend from "./backend/GroupBackend";
const {Option} = Select;
@ -34,7 +33,6 @@ class InvitationEditPage extends React.Component {
invitation: null,
organizations: [],
applications: [],
groups: [],
mode: props.location.mode !== undefined ? props.location.mode : "edit",
};
}
@ -43,7 +41,6 @@ class InvitationEditPage extends React.Component {
this.getInvitation();
this.getOrganizations();
this.getApplicationsByOrganization(this.state.organizationName);
this.getGroupsByOrganization(this.state.organizationName);
}
getInvitation() {
@ -78,17 +75,6 @@ class InvitationEditPage extends React.Component {
});
}
getGroupsByOrganization(organizationName) {
GroupBackend.getGroups(organizationName)
.then((res) => {
if (res.status === "ok") {
this.setState({
groups: res.data,
});
}
});
}
parseInvitationField(key, value) {
if ([""].includes(key)) {
value = Setting.myParseInt(value);
@ -106,22 +92,6 @@ class InvitationEditPage extends React.Component {
});
}
copySignupLink() {
let defaultApplication;
if (this.state.invitation.owner === "built-in") {
defaultApplication = "app-built-in";
} else {
const selectedOrganization = Setting.getArrayItem(this.state.organizations, "name", this.state.invitation.owner);
defaultApplication = selectedOrganization.defaultApplication;
if (!defaultApplication) {
Setting.showMessage("error", i18next.t("invitation:You need to specify a default application for ") + selectedOrganization.name);
return;
}
}
copy(`${window.location.origin}/signup/${defaultApplication}?invitationCode=${this.state.invitation?.defaultCode}`);
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
}
renderInvitation() {
const isCreatedByPlan = this.state.invitation.tag === "auto_created_invitation_for_plan";
return (
@ -130,7 +100,16 @@ class InvitationEditPage extends React.Component {
{this.state.mode === "add" ? i18next.t("invitation:New Invitation") : i18next.t("invitation:Edit Invitation")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button onClick={() => this.submitInvitationEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitInvitationEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
<Button style={{marginLeft: "20px"}} onClick={_ => this.copySignupLink()}>
<Button style={{marginLeft: "20px"}} onClick={() => {
let defaultApplication;
if (this.state.invitation.owner === "built-in") {
defaultApplication = "app-built-in";
} else {
defaultApplication = Setting.getArrayItem(this.state.organizations, "name", this.state.invitation.owner).defaultApplication;
}
copy(`${window.location.origin}/signup/${defaultApplication}?invitationCode=${this.state.invitation?.defaultCode}`);
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
}}>
{i18next.t("application:Copy signup page URL")}
</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteInvitation()}>{i18next.t("general:Cancel")}</Button> : null}
@ -141,7 +120,7 @@ class InvitationEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} disabled={!Setting.isAdminUser(this.props.account) || isCreatedByPlan} value={this.state.invitation.owner} onChange={(value => {this.updateInvitationField("owner", value); this.getApplicationsByOrganization(value);this.getGroupsByOrganization(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.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
}
@ -225,21 +204,6 @@ class InvitationEditPage extends React.Component {
]} />
</Col>
</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"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("signup:Username"), i18next.t("signup:Username - Tooltip"))} :
@ -337,7 +301,16 @@ class InvitationEditPage extends React.Component {
<div style={{marginTop: "20px", marginLeft: "40px"}}>
<Button size="large" onClick={() => this.submitInvitationEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitInvitationEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
<Button style={{marginLeft: "20px"}} size="large" onClick={_ => this.copySignupLink()}>
<Button style={{marginLeft: "20px"}} size="large" onClick={() => {
let defaultApplication;
if (this.state.invitation.owner === "built-in") {
defaultApplication = "app-built-in";
} else {
defaultApplication = Setting.getArrayItem(this.state.organizations, "name", this.state.invitation.owner).defaultApplication;
}
copy(`${window.location.origin}/signup/${defaultApplication}?invitationCode=${this.state.invitation?.defaultCode}`);
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
}}>
{i18next.t("application:Copy signup page URL")}
</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteInvitation()}>{i18next.t("general:Cancel")}</Button> : null}

View File

@ -228,21 +228,6 @@ class LdapEditPage extends React.Component {
/>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{lineHeight: "32px", textAlign: "right", paddingRight: "25px"}} span={3}>
{Setting.getLabel(i18next.t("general:Password type"), i18next.t("general:Password type - Tooltip"))} :
</Col>
<Col span={21}>
<Select virtual={false} style={{width: "100%"}} value={this.state.ldap.passwordType ?? []} onChange={(value => {
this.updateLdapField("passwordType", value);
})}
>
<Option key={"Plain"} value={"Plain"}>{i18next.t("general:Plain")}</Option>
<Option key={"SSHA"} value={"SSHA"} >SSHA</Option>
<Option key={"MD5"} value={"MD5"} >MD5</Option>
</Select>
</Col>
</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"))} :

View File

@ -192,21 +192,17 @@ function ManagementPage(props) {
themeAlgorithm={props.themeAlgorithm}
onChange={props.setLogoAndThemeAlgorithm} />
<LanguageSelect languages={props.account.organization.languages} />
{
Conf.AiAssistantUrl?.trim() && (
<Tooltip title="Click to open AI assistant">
<div className="select-box" onClick={props.openAiAssistant}>
<DeploymentUnitOutlined style={{fontSize: "24px"}} />
</div>
</Tooltip>
)
}
<Tooltip title="Click to open AI assitant">
<div className="select-box" onClick={props.openAiAssistant}>
<DeploymentUnitOutlined style={{fontSize: "24px"}} />
</div>
</Tooltip>
<OpenTour />
{Setting.isAdminUser(props.account) && (props.uri.indexOf("/trees") === -1) &&
{Setting.isAdminUser(props.account) && !Setting.isMobile() && (props.uri.indexOf("/trees") === -1) &&
<OrganizationSelect
initValue={Setting.getOrganization()}
withAll={true}
style={{marginRight: "20px", width: "180px", display: !Setting.isMobile() ? "flex" : "none"}}
style={{marginRight: "20px", width: "180px", display: "flex"}}
onChange={(value) => {
Setting.setOrganization(value);
}}

View File

@ -18,7 +18,11 @@ import * as ModelBackend from "./backend/ModelBackend";
import * as OrganizationBackend from "./backend/OrganizationBackend";
import * as Setting from "./Setting";
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;
@ -143,10 +147,16 @@ class ModelEditPage extends React.Component {
{Setting.getLabel(i18next.t("model:Model text"), i18next.t("model:Model text - Tooltip"))} :
</Col>
<Col span={22}>
<div style={{position: "relative", height: "500px"}} >
<ModelEditor
model={this.state.model}
onModelTextChange={(value) => this.updateModelField("modelText", value)}
<div style={{width: "100%"}} >
<CodeMirror
value={this.state.model.modelText}
options={{mode: "properties", theme: "default"}}
onBeforeChange={(editor, data, value) => {
if (Setting.builtInObject(this.state.model)) {
return;
}
this.updateModelField("modelText", value);
}}
/>
</div>
</Col>

View File

@ -19,7 +19,6 @@ import * as ApplicationBackend from "./backend/ApplicationBackend";
import * as LdapBackend from "./backend/LdapBackend";
import * as Setting from "./Setting";
import * as Conf from "./Conf";
import * as Obfuscator from "./auth/Obfuscator";
import i18next from "i18next";
import {LinkOutlined} from "@ant-design/icons";
import LdapTable from "./table/LdapTable";
@ -113,22 +112,6 @@ 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() {
return (
<Card size="small" title={
@ -311,44 +294,6 @@ class OrganizationEditPage extends React.Component {
/>
</Col>
</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"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Supported country codes"), i18next.t("general:Supported country codes - Tooltip"))} :
@ -360,7 +305,6 @@ class OrganizationEditPage extends React.Component {
}}
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))
}
@ -462,16 +406,6 @@ class OrganizationEditPage extends React.Component {
}} />
</Col>
</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"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("organization:Init score"), i18next.t("organization:Init score - Tooltip"))} :
@ -594,12 +528,6 @@ class OrganizationEditPage extends React.Component {
const organization = Setting.deepCopy(this.state.organization);
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)
.then((res) => {
if (res.status === "ok") {

View File

@ -35,9 +35,6 @@ class OrganizationListPage extends BaseListPage {
passwordType: "plain",
PasswordSalt: "",
passwordOptions: [],
passwordObfuscatorType: "Plain",
passwordObfuscatorKey: "",
passwordExpireDays: 0,
countryCodes: ["US"],
defaultAvatar: `${Setting.StaticBaseUrl}/img/casbin.svg`,
defaultApplication: "",

View File

@ -123,22 +123,6 @@ class ProductBuyPage extends React.Component {
return "$";
} else if (product?.currency === "CNY") {
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 {
return "(Unknown currency)";
}

View File

@ -209,14 +209,6 @@ class ProductEditPage extends React.Component {
[
{id: "USD", name: "USD"},
{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>)
}
</Select>

View File

@ -297,8 +297,6 @@ class ProviderEditPage extends React.Component {
return Setting.getLabel(i18next.t("provider:Scene"), i18next.t("provider:Scene - Tooltip"));
} else if (provider.type === "WeChat Pay") {
return Setting.getLabel(i18next.t("provider:App ID"), i18next.t("provider:App ID - Tooltip"));
} else if (provider.type === "CUCloud") {
return Setting.getLabel(i18next.t("provider:Account ID"), i18next.t("provider:Account ID - Tooltip"));
} else {
return Setting.getLabel(i18next.t("provider:Client ID 2"), i18next.t("provider:Client ID 2 - Tooltip"));
}
@ -395,9 +393,6 @@ class ProviderEditPage extends React.Component {
} else if (provider.type === "Line" || provider.type === "Matrix" || provider.type === "Rocket Chat") {
text = i18next.t("provider:App Key");
tooltip = i18next.t("provider:App Key - Tooltip");
} else if (provider.type === "CUCloud") {
text = i18next.t("provider:Topic name");
tooltip = i18next.t("provider:Topic name - Tooltip");
}
}
@ -638,20 +633,6 @@ class ProviderEditPage extends React.Component {
</React.Fragment>
)
}
{
this.state.provider.category === "OAuth" ? (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Email regex"), i18next.t("provider:Email regex - Tooltip"))} :
</Col>
<Col span={22}>
<TextArea rows={4} value={this.state.provider.emailRegex} onChange={e => {
this.updateProviderField("emailRegex", e.target.value);
}} />
</Col>
</Row>
) : null
}
{
this.state.provider.type === "Custom" ? (
<React.Fragment>
@ -776,7 +757,7 @@ class ProviderEditPage extends React.Component {
)
}
{
this.state.provider.category !== "Email" && this.state.provider.type !== "WeChat" && this.state.provider.type !== "Apple" && this.state.provider.type !== "Aliyun Captcha" && this.state.provider.type !== "WeChat Pay" && this.state.provider.type !== "Twitter" && this.state.provider.type !== "Reddit" && this.state.provider.type !== "CUCloud" ? null : (
this.state.provider.category !== "Email" && this.state.provider.type !== "WeChat" && this.state.provider.type !== "Apple" && this.state.provider.type !== "Aliyun Captcha" && this.state.provider.type !== "WeChat Pay" && this.state.provider.type !== "Twitter" && this.state.provider.type !== "Reddit" ? null : (
<React.Fragment>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
@ -789,7 +770,7 @@ class ProviderEditPage extends React.Component {
</Col>
</Row>
{
(this.state.provider.type === "WeChat Pay" || this.state.provider.type === "CUCloud") || (this.state.provider.category === "Email" && (this.state.provider.type === "Azure ACS" || this.state.provider.type === "SendGrid")) ? null : (
(this.state.provider.type === "WeChat Pay") || (this.state.provider.category === "Email" && (this.state.provider.type === "Azure ACS" || this.state.provider.type === "SendGrid")) ? null : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{this.getClientSecret2Label(this.state.provider)} :
@ -875,9 +856,9 @@ class ProviderEditPage extends React.Component {
</Row>
)
}
{this.state.provider.category === "Storage" || ["Custom HTTP SMS", "Custom HTTP Email", "CUCloud"].includes(this.state.provider.type) ? (
{this.state.provider.category === "Storage" || ["Custom HTTP SMS", "Custom HTTP Email"].includes(this.state.provider.type) ? (
<div>
{["Local File System", "CUCloud"].includes(this.state.provider.type) ? null : (
{["Local File System"].includes(this.state.provider.type) ? null : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Endpoint"), i18next.t("provider:Region endpoint for Internet"))} :
@ -889,7 +870,7 @@ class ProviderEditPage extends React.Component {
</Col>
</Row>
)}
{["Custom HTTP SMS", "Local File System", "MinIO", "Tencent Cloud COS", "Google Cloud Storage", "Qiniu Cloud Kodo", "Synology", "Casdoor", "CUCloud"].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"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Endpoint (Intranet)"), i18next.t("provider:Region endpoint for Intranet"))} :
@ -901,7 +882,7 @@ class ProviderEditPage extends React.Component {
</Col>
</Row>
)}
{["Custom HTTP SMS", "Local File System", "CUCloud"].includes(this.state.provider.type) ? null : (
{["Custom HTTP SMS", "Local File System"].includes(this.state.provider.type) ? null : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{["Casdoor"].includes(this.state.provider.type) ?
@ -915,7 +896,7 @@ class ProviderEditPage extends React.Component {
</Col>
</Row>
)}
{["Custom HTTP SMS", "CUCloud"].includes(this.state.provider.type) ? null : (
{["Custom HTTP SMS"].includes(this.state.provider.type) ? null : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Path prefix"), i18next.t("provider:Path prefix - Tooltip"))} :
@ -927,7 +908,7 @@ class ProviderEditPage extends React.Component {
</Col>
</Row>
)}
{["Custom HTTP SMS", "Synology", "Casdoor", "CUCloud"].includes(this.state.provider.type) ? null : (
{["Custom HTTP SMS", "Qiniu Cloud Kodo", "Synology", "Casdoor"].includes(this.state.provider.type) ? null : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
@ -951,7 +932,7 @@ class ProviderEditPage extends React.Component {
</Col>
</Row>
) : null}
{["AWS S3", "Tencent Cloud COS", "Qiniu Cloud Kodo", "Casdoor", "CUCloud OSS", "MinIO", "CUCloud"].includes(this.state.provider.type) ? (
{["AWS S3", "Tencent Cloud COS", "Qiniu Cloud Kodo", "Casdoor"].includes(this.state.provider.type) ? (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{["Casdoor"].includes(this.state.provider.type) ?
@ -990,7 +971,7 @@ class ProviderEditPage extends React.Component {
</Col>
</Row>
) : null}
{["Custom HTTP", "CUCloud"].includes(this.state.provider.type) ? (
{["Custom HTTP"].includes(this.state.provider.type) ? (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Parameter"), i18next.t("provider:Parameter - Tooltip"))} :
@ -1002,7 +983,7 @@ class ProviderEditPage extends React.Component {
</Col>
</Row>
) : null}
{["Google Chat", "CUCloud"].includes(this.state.provider.type) ? (
{["Google Chat"].includes(this.state.provider.type) ? (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Metadata"), i18next.t("provider:Metadata - Tooltip"))} :

View File

@ -187,7 +187,7 @@ class RoleEditPage extends React.Component {
{Setting.getLabel(i18next.t("role:Sub users"), i18next.t("role:Sub users - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={true} mode="multiple" style={{width: "100%"}} value={this.state.role.users}
<Select virtual={false} mode="multiple" style={{width: "100%"}} value={this.state.role.users}
onChange={(value => {this.updateRoleField("users", value);})}
options={this.state.users.map((user) => Setting.getOption(`${user.owner}/${user.name}`, `${user.owner}/${user.name}`))}
/>

View File

@ -233,10 +233,6 @@ export const OtherProviderInfo = {
logo: `${StaticBaseUrl}/img/casdoor.png`,
url: "https://casdoor.org/docs/provider/storage/overview",
},
"CUCloud OSS": {
logo: `${StaticBaseUrl}/img/social_cucloud.png`,
url: "https://www.cucloud.cn/product/oss.html",
},
},
SAML: {
"Aliyun IDaaS": {
@ -405,10 +401,6 @@ export const OtherProviderInfo = {
logo: `${StaticBaseUrl}/img/social_viber.png`,
url: "https://www.viber.com/",
},
"CUCloud": {
logo: `${StaticBaseUrl}/img/cucloud.png`,
url: "https://www.cucloud.cn/",
},
},
};
@ -426,9 +418,6 @@ export function getCountryCode(country) {
}
export function getCountryCodeData(countryCodes = phoneNumber.getCountries()) {
if (countryCodes?.includes("All")) {
countryCodes = phoneNumber.getCountries();
}
return countryCodes?.map((countryCode) => {
if (phoneNumber.isSupportedCountry(countryCode)) {
const name = initCountries().getName(countryCode, getLanguage());
@ -447,10 +436,10 @@ export function getCountryCodeOption(country) {
<Option key={country.code} value={country.code} label={`+${country.phone}`} text={`${country.name}, ${country.code}, ${country.phone}`} >
<div style={{display: "flex", justifyContent: "space-between", marginRight: "10px"}}>
<div>
{country.code === "All" ? null : getCountryImage(country)}
{getCountryImage(country)}
{`${country.name}`}
</div>
{country.code === "All" ? null : `+${country.phone}`}
{`+${country.phone}`}
</div>
</Option>
);
@ -928,7 +917,7 @@ export function getClickable(text) {
return (
<a onClick={() => {
copy(text);
showMessage("success", i18next.t("general:Copied to clipboard successfully"));
showMessage("success", "Copied to clipboard");
}}>
{text}
</a>
@ -989,7 +978,6 @@ export function getProviderTypeOptions(category) {
{id: "Bilibili", name: "Bilibili"},
{id: "Okta", name: "Okta"},
{id: "Douyin", name: "Douyin"},
{id: "Kwai", name: "Kwai"},
{id: "Line", name: "Line"},
{id: "Amazon", name: "Amazon"},
{id: "Auth0", name: "Auth0"},
@ -1087,7 +1075,6 @@ export function getProviderTypeOptions(category) {
{id: "Google Cloud Storage", name: "Google Cloud Storage"},
{id: "Synology", name: "Synology"},
{id: "Casdoor", name: "Casdoor"},
{id: "CUCloud OSS", name: "CUCloud OSS"},
]
);
} else if (category === "SAML") {
@ -1141,7 +1128,6 @@ export function getProviderTypeOptions(category) {
{id: "Reddit", name: "Reddit"},
{id: "Rocket Chat", name: "Rocket Chat"},
{id: "Viber", name: "Viber"},
{id: "CUCloud", name: "CUCloud"},
]);
} else {
return [];
@ -1182,7 +1168,7 @@ export function renderLogo(application) {
function isSigninMethodEnabled(application, signinMethod) {
if (application && application.signinMethods) {
return application.signinMethods.filter(item => item.name === signinMethod && item.rule !== "Hide password").length > 0;
return application.signinMethods.filter(item => item.name === signinMethod).length > 0;
} else {
return false;
}
@ -1561,30 +1547,10 @@ export function getDefaultHtmlEmailContent() {
export function getCurrencyText(product) {
if (product?.currency === "USD") {
return i18next.t("currency:USD");
return i18next.t("product:USD");
} else if (product?.currency === "CNY") {
return i18next.t("currency:CNY");
} else if (product?.currency === "EUR") {
return i18next.t("currency:EUR");
} else if (product?.currency === "JPY") {
return i18next.t("currency:JPY");
} else if (product?.currency === "GBP") {
return i18next.t("currency:GBP");
} else if (product?.currency === "AUD") {
return i18next.t("currency:AUD");
} else if (product?.currency === "CAD") {
return i18next.t("currency:CAD");
} else if (product?.currency === "CHF") {
return i18next.t("currency:CHF");
} else if (product?.currency === "HKD") {
return i18next.t("currency:HKD");
} else if (product?.currency === "SGD") {
return i18next.t("currency:SGD");
return i18next.t("product:CNY");
} else {
return "(Unknown currency)";
}
}
export function isDarkTheme(themeAlgorithm) {
return themeAlgorithm && themeAlgorithm.includes("dark");
}

View File

@ -434,9 +434,10 @@ class SyncerEditPage extends React.Component {
{Setting.getLabel(i18next.t("syncer:Table"), i18next.t("syncer:Table - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.syncer.table} onChange={e => {
this.updateSyncerField("table", e.target.value);
}} />
<Input value={this.state.syncer.table}
disabled={this.state.syncer.type === "Keycloak"} onChange={e => {
this.updateSyncerField("table", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >

View File

@ -1009,19 +1009,6 @@ class UserEditPage extends React.Component {
</Col>
</Row>
);
} else if (accountItem.name === "Last change password time") {
return (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("user:Last change password time"), i18next.t("user:Last change password time"))} :
</Col>
<Col span={22}>
<Input value={this.state.user.lastChangePasswordTime} onChange={e => {
this.updateUserField("lastChangePasswordTime", e.target.value);
}} />
</Col>
</Row>
);
} else if (accountItem.name === "Managed accounts") {
return (
<Row style={{marginTop: "20px"}} >
@ -1063,8 +1050,6 @@ class UserEditPage extends React.Component {
<MfaAccountTable
title={i18next.t("user:MFA accounts")}
table={this.state.user.mfaAccounts}
accessToken={this.props.account?.accessToken}
icon={this.state.user.avatar}
onUpdateTable={(table) => {this.updateUserField("mfaAccounts", table);}}
/>
</Col>
@ -1083,19 +1068,6 @@ class UserEditPage extends React.Component {
</Col>
</Row>
);
} else if (accountItem.name === "IP whitelist") {
return (
<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.user.ipWhitelist} onChange={e => {
this.updateUserField("ipWhitelist", e.target.value);
}} />
</Col>
</Row>
);
}
}

View File

@ -204,7 +204,7 @@ class AuthCallback extends React.Component {
}
const SAMLResponse = res.data;
const redirectUri = res.data2.redirectUrl;
Setting.goToLink(`${redirectUri}${redirectUri.includes("?") ? "&" : "?"}SAMLResponse=${encodeURIComponent(SAMLResponse)}&RelayState=${oAuthParams.relayState}`);
Setting.goToLink(`${redirectUri}?SAMLResponse=${encodeURIComponent(SAMLResponse)}&RelayState=${oAuthParams.relayState}`);
}
}
} else {

View File

@ -34,42 +34,25 @@ class CasLogout extends React.Component {
UNSAFE_componentWillMount() {
const params = new URLSearchParams(this.props.location.search);
const logoutInterval = 100;
const logoutTimeOut = (redirectUri) => {
setTimeout(() => {
AuthBackend.getAccount().then((accountRes) => {
if (accountRes.status === "ok") {
AuthBackend.logout().then((logoutRes) => {
if (logoutRes.status === "ok") {
logoutTimeOut(logoutRes.data2);
} else {
Setting.showMessage("error", `${i18next.t("login:Failed to log out")}: ${logoutRes.msg}`);
}
});
} else {
Setting.showMessage("success", i18next.t("application:Logged out successfully"));
this.props.onUpdateAccount(null);
if (redirectUri !== null && redirectUri !== undefined && redirectUri !== "") {
Setting.goToLink(redirectUri);
} else if (params.has("service")) {
Setting.goToLink(params.get("service"));
} else {
Setting.goToLinkSoft(this, `/cas/${this.state.owner}/${this.state.applicationName}/login`);
}
}
});
}, logoutInterval);
};
AuthBackend.logout()
.then((res) => {
if (res.status === "ok") {
logoutTimeOut(res.data2);
Setting.showMessage("success", "Logged out successfully");
this.props.onUpdateAccount(null);
const redirectUri = res.data2;
if (redirectUri !== null && redirectUri !== undefined && redirectUri !== "") {
Setting.goToLink(redirectUri);
} else if (params.has("service")) {
Setting.goToLink(params.get("service"));
} else {
Setting.goToLinkSoft(this, `/cas/${this.state.owner}/${this.state.applicationName}/login`);
}
} else {
Setting.showMessage("error", `${i18next.t("login:Failed to log out")}: ${res.msg}`);
Setting.showMessage("error", `Failed to log out: ${res.msg}`);
}
});
}
render() {

View File

@ -1,31 +0,0 @@
// 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 {createButton} from "react-social-login-buttons";
import {StaticBaseUrl} from "../Setting";
function Icon({width = 24, height = 24}) {
return <img src={`${StaticBaseUrl}/buttons/kwai.svg`} alt="Sign in with Kwai" style={{width: width, height: height}} />;
}
const config = {
text: "Sign in with Kwai",
icon: Icon,
style: {background: "#ffffff", color: "#000000"},
activeStyle: {background: "#ededee"},
};
const KwaiLoginButton = createButton(config);
export default KwaiLoginButton;

View File

@ -19,7 +19,6 @@ import {withRouter} from "react-router-dom";
import * as UserWebauthnBackend from "../backend/UserWebauthnBackend";
import OrganizationSelect from "../common/select/OrganizationSelect";
import * as Conf from "../Conf";
import * as Obfuscator from "./Obfuscator";
import * as AuthBackend from "./AuthBackend";
import * as OrganizationBackend from "../backend/OrganizationBackend";
import * as ApplicationBackend from "../backend/ApplicationBackend";
@ -52,6 +51,7 @@ class LoginPage extends React.Component {
username: null,
validEmailOrPhone: false,
validEmail: false,
enableCaptchaModal: CaptchaRule.Never,
openCaptchaModal: false,
openFaceRecognitionModal: false,
verifyCaptcha: undefined,
@ -92,6 +92,17 @@ class LoginPage extends React.Component {
}
if (prevProps.application !== this.props.application) {
this.setState({loginMethod: this.getDefaultLoginMethod(this.props.application)});
const captchaProviderItems = this.getCaptchaProviderItems(this.props.application);
if (captchaProviderItems) {
if (captchaProviderItems.some(providerItem => providerItem.rule === "Always")) {
this.setState({enableCaptchaModal: CaptchaRule.Always});
} else if (captchaProviderItems.some(providerItem => providerItem.rule === "Dynamic")) {
this.setState({enableCaptchaModal: CaptchaRule.Dynamic});
} else {
this.setState({enableCaptchaModal: CaptchaRule.Never});
}
}
}
if (prevProps.account !== this.props.account && this.props.account !== undefined) {
@ -121,19 +132,6 @@ class LoginPage extends React.Component {
}
}
getCaptchaRule(application) {
const captchaProviderItems = this.getCaptchaProviderItems(application);
if (captchaProviderItems) {
if (captchaProviderItems.some(providerItem => providerItem.rule === "Always")) {
return CaptchaRule.Always;
} else if (captchaProviderItems.some(providerItem => providerItem.rule === "Dynamic")) {
return CaptchaRule.Dynamic;
} else {
return CaptchaRule.Never;
}
}
}
checkCaptchaStatus(values) {
AuthBackend.getCaptchaStatus(values)
.then((res) => {
@ -227,26 +225,7 @@ class LoginPage extends React.Component {
return "password";
}
getCurrentLoginMethod() {
if (this.state.loginMethod === "password") {
return "Password";
} else if (this.state.loginMethod?.includes("verificationCode")) {
return "Verification code";
} else if (this.state.loginMethod === "webAuthn") {
return "WebAuthn";
} else if (this.state.loginMethod === "ldap") {
return "LDAP";
} else if (this.state.loginMethod === "faceId") {
return "Face ID";
} else {
return "Password";
}
}
getPlaceholder(defaultPlaceholder = null) {
if (defaultPlaceholder) {
return defaultPlaceholder;
}
getPlaceholder() {
switch (this.state.loginMethod) {
case "verificationCode": return i18next.t("login:Email or phone");
case "verificationCodeEmail": return i18next.t("login:Email");
@ -281,7 +260,17 @@ class LoginPage extends React.Component {
values["organization"] = this.getApplicationObj().organization;
}
values["signinMethod"] = this.getCurrentLoginMethod();
if (this.state.loginMethod === "password") {
values["signinMethod"] = "Password";
} else if (this.state.loginMethod?.includes("verificationCode")) {
values["signinMethod"] = "Verification code";
} else if (this.state.loginMethod === "webAuthn") {
values["signinMethod"] = "WebAuthn";
} else if (this.state.loginMethod === "ldap") {
values["signinMethod"] = "LDAP";
} else if (this.state.loginMethod === "faceId") {
values["signinMethod"] = "Face ID";
}
const oAuthParams = Util.getOAuthGetParameters();
values["type"] = oAuthParams?.responseType ?? this.state.type;
@ -390,22 +379,13 @@ class LoginPage extends React.Component {
return;
}
if (this.state.loginMethod === "password" || this.state.loginMethod === "ldap") {
const organization = this.getApplicationObj()?.organizationObj;
const [passwordCipher, errorMessage] = Obfuscator.encryptByPasswordObfuscator(organization?.passwordObfuscatorType, organization?.passwordObfuscatorKey, values["password"]);
if (errorMessage.length > 0) {
Setting.showMessage("error", errorMessage);
return;
} else {
values["password"] = passwordCipher;
}
const captchaRule = this.getCaptchaRule(this.getApplicationObj());
if (captchaRule === CaptchaRule.Always) {
if (this.state.enableCaptchaModal === CaptchaRule.Always) {
this.setState({
openCaptchaModal: true,
values: values,
});
return;
} else if (captchaRule === CaptchaRule.Dynamic) {
} else if (this.state.enableCaptchaModal === CaptchaRule.Dynamic) {
this.checkCaptchaStatus(values);
return;
}
@ -418,7 +398,6 @@ class LoginPage extends React.Component {
if (this.state.type === "cas") {
// CAS
const casParams = Util.getCasParameters();
values["signinMethod"] = this.getCurrentLoginMethod();
values["type"] = this.state.type;
AuthBackend.loginCas(values, casParams).then((res) => {
const loginHandler = (res) => {
@ -447,8 +426,8 @@ class LoginPage extends React.Component {
formValues={values}
authParams={casParams}
application={this.getApplicationObj()}
onFail={(errorMessage) => {
Setting.showMessage("error", errorMessage);
onFail={() => {
Setting.showMessage("error", i18next.t("mfa:Verification failed"));
}}
onSuccess={(res) => loginHandler(res)}
/>);
@ -488,10 +467,6 @@ class LoginPage extends React.Component {
const accessToken = res.data;
Setting.goToLink(`${oAuthParams.redirectUri}#${amendatoryResponseType}=${accessToken}&state=${oAuthParams.state}&token_type=bearer`);
} else if (responseType === "saml") {
if (res.data === RequiredMfa) {
this.props.onLoginSuccess(window.location.href);
return;
}
if (res.data2.needUpdatePassword) {
sessionStorage.setItem("signinUrl", window.location.href);
Setting.goToLink(this, `/forget/${this.state.applicationName}`);
@ -505,7 +480,7 @@ class LoginPage extends React.Component {
} else {
const SAMLResponse = res.data;
const redirectUri = res.data2.redirectUrl;
Setting.goToLink(`${redirectUri}${redirectUri.includes("?") ? "&" : "?"}SAMLResponse=${encodeURIComponent(SAMLResponse)}&RelayState=${oAuthParams.relayState}`);
Setting.goToLink(`${redirectUri}?SAMLResponse=${encodeURIComponent(SAMLResponse)}&RelayState=${oAuthParams.relayState}`);
}
}
};
@ -520,8 +495,8 @@ class LoginPage extends React.Component {
formValues={values}
authParams={oAuthParams}
application={this.getApplicationObj()}
onFail={(errorMessage) => {
Setting.showMessage("error", errorMessage);
onFail={() => {
Setting.showMessage("error", i18next.t("mfa:Verification failed"));
}}
onSuccess={(res) => loginHandler(res)}
/>);
@ -686,7 +661,7 @@ class LoginPage extends React.Component {
id="input"
className="login-username-input"
prefix={<UserOutlined className="site-form-item-icon" />}
placeholder={this.getPlaceholder(signinItem.placeholder)}
placeholder={this.getPlaceholder()}
onChange={e => {
this.setState({
username: e.target.value,
@ -927,7 +902,7 @@ class LoginPage extends React.Component {
}
renderCaptchaModal(application) {
if (this.getCaptchaRule(this.getApplicationObj()) === CaptchaRule.Never) {
if (this.state.enableCaptchaModal === CaptchaRule.Never) {
return null;
}
const captchaProviderItems = this.getCaptchaProviderItems(application);
@ -963,7 +938,7 @@ class LoginPage extends React.Component {
signinItem.label ? Setting.renderSignupLink(application, signinItem.label) :
(
<React.Fragment>
{i18next.t("login:No account?")}&nbsp;
{i18next.t("login:No account?")}
{
Setting.renderSignupLink(application, i18next.t("login:sign up now"))
}
@ -1100,7 +1075,7 @@ class LoginPage extends React.Component {
className="login-password-input"
prefix={<LockOutlined className="site-form-item-icon" />}
type="password"
placeholder={signinItem.placeholder ? signinItem.placeholder : i18next.t("general:Password")}
placeholder={i18next.t("general:Password")}
disabled={this.state.loginMethod === "password" ? !Setting.isPasswordEnabled(application) : !Setting.isLdapEnabled(application)}
/>
</Form.Item>
@ -1150,9 +1125,6 @@ class LoginPage extends React.Component {
]);
application?.signinMethods?.forEach((signinMethod) => {
if (signinMethod.rule === "Hide password") {
return;
}
const item = itemsMap.get(generateItemKey(signinMethod.name, signinMethod.rule));
if (item) {
let label = signinMethod.name === signinMethod.displayName ? item.label : signinMethod.displayName;
@ -1307,7 +1279,7 @@ class LoginPage extends React.Component {
<div className="login-content" style={{margin: this.props.preview ?? this.parseOffset(application.formOffset)}}>
{Setting.inIframe() || Setting.isMobile() ? null : <div dangerouslySetInnerHTML={{__html: application.formCss}} />}
{Setting.inIframe() || !Setting.isMobile() ? null : <div dangerouslySetInnerHTML={{__html: application.formCssMobile}} />}
<div className={Setting.isDarkTheme(this.props.themeAlgorithm) ? "login-panel-dark" : "login-panel"}>
<div className="login-panel">
<div className="side-image" style={{display: application.formOffset !== 4 ? "none" : null}}>
<div dangerouslySetInnerHTML={{__html: application.formSideHtml}} />
</div>

View File

@ -37,7 +37,7 @@ class MfaSetupPage extends React.Component {
this.state = {
account: props.account,
application: null,
applicationName: props.account.signupApplication ?? localStorage.getItem("applicationName") ?? "",
applicationName: props.account.signupApplication ?? "",
current: location.state?.from !== undefined ? 1 : 0,
mfaProps: null,
mfaType: params.get("mfaType") ?? SmsMfaType,
@ -179,10 +179,8 @@ class MfaSetupPage extends React.Component {
mfaProps={this.state.mfaProps}
application={this.state.application}
user={this.props.account}
onSuccess={(res) => {
onSuccess={() => {
this.setState({
dest: res.dest,
countryCode: res.countryCode,
current: this.state.current + 1,
});
}}
@ -197,7 +195,7 @@ class MfaSetupPage extends React.Component {
);
case 2:
return (
<MfaEnableForm user={this.getUser()} mfaType={this.state.mfaType} secret={this.state.mfaProps.secret} recoveryCodes={this.state.mfaProps.recoveryCodes} dest={this.state.dest} countryCode={this.state.countryCode}
<MfaEnableForm user={this.getUser()} mfaType={this.state.mfaType} recoveryCodes={this.state.mfaProps.recoveryCodes}
onSuccess={() => {
Setting.showMessage("success", i18next.t("general:Enabled successfully"));
this.props.onfinish();

View File

@ -1,95 +0,0 @@
// 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 CryptoJS from "crypto-js";
import {Buffer} from "buffer";
export function getRandomKeyForObfuscator(obfuscatorType) {
if (obfuscatorType === "DES") {
return getRandomHexKey(16);
} else if (obfuscatorType === "AES") {
return getRandomHexKey(32);
} else {
return "";
}
}
export const passwordObfuscatorKeyRegexes = {
"DES": /^[1-9a-f]{16}$/,
"AES": /^[1-9a-f]{32}$/,
};
function encrypt(cipher, key, iv, password) {
const encrypted = cipher.encrypt(
CryptoJS.enc.Hex.parse(Buffer.from(password, "utf-8").toString("hex")),
CryptoJS.enc.Hex.parse(key),
{
iv: iv,
mode: CryptoJS.mode.CBC,
pad: CryptoJS.pad.Pkcs7,
}
);
return iv.concat(encrypted.ciphertext).toString(CryptoJS.enc.Hex);
}
export function checkPasswordObfuscator(passwordObfuscatorType, passwordObfuscatorKey) {
if (passwordObfuscatorType === undefined) {
return "passwordObfuscatorType should not be undefined";
} else if (passwordObfuscatorType === "Plain" || passwordObfuscatorType === "") {
return "";
} else if (passwordObfuscatorType === "AES" || passwordObfuscatorType === "DES") {
if (passwordObfuscatorKeyRegexes[passwordObfuscatorType].test(passwordObfuscatorKey)) {
return "";
} else {
return `The password obfuscator key doesn't match the regex: ${passwordObfuscatorKeyRegexes[passwordObfuscatorType].source}`;
}
} else {
return `unsupported password obfuscator type: ${passwordObfuscatorType}`;
}
}
export function encryptByPasswordObfuscator(passwordObfuscatorType, passwordObfuscatorKey, password) {
const passwordObfuscatorErrorMessage = checkPasswordObfuscator(passwordObfuscatorType, passwordObfuscatorKey);
if (passwordObfuscatorErrorMessage.length > 0) {
return ["", passwordObfuscatorErrorMessage];
} else {
if (passwordObfuscatorType === "Plain" || passwordObfuscatorType === "") {
return [password, ""];
} else if (passwordObfuscatorType === "AES") {
return [encryptByAes(passwordObfuscatorKey, password), ""];
} else if (passwordObfuscatorType === "DES") {
return [encryptByDes(passwordObfuscatorKey, password), ""];
}
}
}
function encryptByDes(key, password) {
const iv = CryptoJS.lib.WordArray.random(8);
return encrypt(CryptoJS.DES, key, iv, password);
}
function encryptByAes(key, password) {
const iv = CryptoJS.lib.WordArray.random(16);
return encrypt(CryptoJS.AES, key, iv, password);
}
function getRandomHexKey(length) {
const characters = "123456789abcdef";
let key = "";
for (let i = 0; i < length; i++) {
const randomIndex = Math.floor(Math.random() * characters.length);
key += characters[randomIndex];
}
return key;
}

View File

@ -119,10 +119,6 @@ const authInfo = {
scope: "user_info",
endpoint: "https://open.douyin.com/platform/oauth/connect",
},
Kwai: {
scope: "user_info",
endpoint: "https://open.kuaishou.com/oauth2/connect",
},
Custom: {
endpoint: "https://example.com/",
},
@ -474,8 +470,6 @@ export function getAuthUrl(application, provider, method, code) {
return `${provider.domain}/v1/authorize?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
} else if (provider.type === "Douyin" || provider.type === "TikTok") {
return `${endpoint}?client_key=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
} else if (provider.type === "Kwai") {
return `${endpoint}?app_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
} else if (provider.type === "Custom") {
return `${provider.customAuthUrl}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${provider.scopes}&response_type=code&state=${state}`;
} else if (provider.type === "Bilibili") {

Some files were not shown because too many files have changed in this diff Show More