mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-16 05:13:49 +08:00
Compare commits
97 Commits
revert-301
...
v1.720.0
Author | SHA1 | Date | |
---|---|---|---|
2417ff84e6 | |||
468631e654 | |||
e1dea9f697 | |||
c0f22bae43 | |||
c9635d9e2b | |||
3bd52172ea | |||
bf730050d5 | |||
5b733b7f15 | |||
034f28def9 | |||
c86ac8e6ad | |||
d647eed22a | |||
717c53f6e5 | |||
097adac871 | |||
74543b9533 | |||
110dc04179 | |||
6464bd10dc | |||
db878a890e | |||
12d6d8e6ce | |||
8ed6e4f934 | |||
ed9732caf9 | |||
0de4e7da38 | |||
a330fbc11f | |||
ed158d4981 | |||
8df965b98d | |||
2c3749820e | |||
0b17cb9746 | |||
e2ce9ad625 | |||
64491abc64 | |||
934a8947c8 | |||
943edfb48b | |||
0d02b5e768 | |||
ba8d0b5f46 | |||
973a1df6c2 | |||
05bfd3a3a3 | |||
69aa3c8a8b | |||
a1b010a406 | |||
89e92cbd47 | |||
d4c8193357 | |||
9b33800b4c | |||
ec98785172 | |||
45dd4cc344 | |||
1adb172d6b | |||
c08f2b1f3f | |||
62bb257c6d | |||
230a77e3e3 | |||
dce0a96dea | |||
65563fa0cd | |||
f2a94f671a | |||
1460a0498f | |||
adc63ea726 | |||
0b8be016c5 | |||
986dcbbda1 | |||
7d3920fb1f | |||
b794ef87ee | |||
a0d6f2125e | |||
85cbb7d074 | |||
fdc1be9452 | |||
2bd7dabd33 | |||
9b9a58e7ac | |||
38e389e8c8 | |||
ab5fcf848e | |||
b4e51b4631 | |||
45e25acc80 | |||
97dcf24a91 | |||
4c0fff66ff | |||
e7230700e0 | |||
f21aa9c0d2 | |||
4b2b875b2d | |||
df2a5681cc | |||
ac102480c7 | |||
feff47d2dc | |||
79b934d6c2 | |||
365449695b | |||
55a52093e8 | |||
e65fdeb1e0 | |||
a46c1cc775 | |||
5629343466 | |||
3718d2dc04 | |||
38b9ad1d9f | |||
5a92411006 | |||
52eaf6c822 | |||
cc84709151 | |||
22fca78be9 | |||
effd257040 | |||
a38747d90e | |||
da70682cd1 | |||
4a3bd84f84 | |||
7f2869cecb | |||
cef2ab213b | |||
cc979c310e | |||
13d73732ce | |||
5686fe5d22 | |||
d8cb82f67a | |||
cad2e1bcc3 | |||
52cc2e4fa7 | |||
8077a2ccba | |||
4cb8e4a514 |
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@ -35,7 +35,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 20
|
||||||
cache: 'yarn'
|
cache: 'yarn'
|
||||||
cache-dependency-path: ./web/yarn.lock
|
cache-dependency-path: ./web/yarn.lock
|
||||||
- run: yarn install && CI=false yarn run build
|
- run: yarn install && CI=false yarn run build
|
||||||
@ -101,7 +101,7 @@ jobs:
|
|||||||
working-directory: ./
|
working-directory: ./
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 20
|
||||||
cache: 'yarn'
|
cache: 'yarn'
|
||||||
cache-dependency-path: ./web/yarn.lock
|
cache-dependency-path: ./web/yarn.lock
|
||||||
- run: yarn install
|
- run: yarn install
|
||||||
@ -138,7 +138,7 @@ jobs:
|
|||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 20
|
||||||
|
|
||||||
- name: Fetch Previous version
|
- name: Fetch Previous version
|
||||||
id: get-previous-tag
|
id: get-previous-tag
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
<a href="https://github.com/casdoor/casdoor/releases/latest">
|
<a href="https://github.com/casdoor/casdoor/releases/latest">
|
||||||
<img alt="GitHub Release" src="https://img.shields.io/github/v/release/casdoor/casdoor.svg">
|
<img alt="GitHub Release" src="https://img.shields.io/github/v/release/casdoor/casdoor.svg">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://hub.docker.com/repository/docker/casbin/casdoor">
|
<a href="https://hub.docker.com/r/casbin/casdoor">
|
||||||
<img alt="Docker Image Version (latest semver)" src="https://img.shields.io/badge/Docker%20Hub-latest-brightgreen">
|
<img alt="Docker Image Version (latest semver)" src="https://img.shields.io/badge/Docker%20Hub-latest-brightgreen">
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
@ -77,6 +77,7 @@ p, *, *, POST, /api/verify-code, *, *
|
|||||||
p, *, *, POST, /api/reset-email-or-phone, *, *
|
p, *, *, POST, /api/reset-email-or-phone, *, *
|
||||||
p, *, *, POST, /api/upload-resource, *, *
|
p, *, *, POST, /api/upload-resource, *, *
|
||||||
p, *, *, GET, /.well-known/openid-configuration, *, *
|
p, *, *, GET, /.well-known/openid-configuration, *, *
|
||||||
|
p, *, *, GET, /.well-known/webfinger, *, *
|
||||||
p, *, *, *, /.well-known/jwks, *, *
|
p, *, *, *, /.well-known/jwks, *, *
|
||||||
p, *, *, GET, /api/get-saml-login, *, *
|
p, *, *, GET, /api/get-saml-login, *, *
|
||||||
p, *, *, POST, /api/acs, *, *
|
p, *, *, POST, /api/acs, *, *
|
||||||
|
@ -26,6 +26,10 @@ func GetCaptchaProvider(captchaType string) CaptchaProvider {
|
|||||||
return NewDefaultCaptchaProvider()
|
return NewDefaultCaptchaProvider()
|
||||||
case "reCAPTCHA":
|
case "reCAPTCHA":
|
||||||
return NewReCaptchaProvider()
|
return NewReCaptchaProvider()
|
||||||
|
case "reCAPTCHA v2":
|
||||||
|
return NewReCaptchaProvider()
|
||||||
|
case "reCAPTCHA v3":
|
||||||
|
return NewReCaptchaProvider()
|
||||||
case "Aliyun Captcha":
|
case "Aliyun Captcha":
|
||||||
return NewAliyunCaptchaProvider()
|
return NewAliyunCaptchaProvider()
|
||||||
case "hCaptcha":
|
case "hCaptcha":
|
||||||
|
@ -21,7 +21,9 @@ originFrontend =
|
|||||||
staticBaseUrl = "https://cdn.casbin.org"
|
staticBaseUrl = "https://cdn.casbin.org"
|
||||||
isDemoMode = false
|
isDemoMode = false
|
||||||
batchSize = 100
|
batchSize = 100
|
||||||
|
enableErrorMask = false
|
||||||
enableGzip = true
|
enableGzip = true
|
||||||
|
inactiveTimeoutMinutes =
|
||||||
ldapServerPort = 389
|
ldapServerPort = 389
|
||||||
radiusServerPort = 1812
|
radiusServerPort = 1812
|
||||||
radiusSecret = "secret"
|
radiusSecret = "secret"
|
||||||
|
@ -200,6 +200,10 @@ func (c *ApiController) Signup() {
|
|||||||
Type: userType,
|
Type: userType,
|
||||||
Password: authForm.Password,
|
Password: authForm.Password,
|
||||||
DisplayName: authForm.Name,
|
DisplayName: authForm.Name,
|
||||||
|
Gender: authForm.Gender,
|
||||||
|
Bio: authForm.Bio,
|
||||||
|
Tag: authForm.Tag,
|
||||||
|
Education: authForm.Education,
|
||||||
Avatar: organization.DefaultAvatar,
|
Avatar: organization.DefaultAvatar,
|
||||||
Email: authForm.Email,
|
Email: authForm.Email,
|
||||||
Phone: authForm.Phone,
|
Phone: authForm.Phone,
|
||||||
@ -234,6 +238,10 @@ func (c *ApiController) Signup() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if invitation != nil && invitation.SignupGroup != "" {
|
||||||
|
user.Groups = []string{invitation.SignupGroup}
|
||||||
|
}
|
||||||
|
|
||||||
affected, err := object.AddUser(user)
|
affected, err := object.AddUser(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
|
@ -463,6 +463,15 @@ func (c *ApiController) Login() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
password := authForm.Password
|
password := authForm.Password
|
||||||
|
|
||||||
|
if application.OrganizationObj != nil {
|
||||||
|
password, err = util.GetUnobfuscatedPassword(application.OrganizationObj.PasswordObfuscatorType, application.OrganizationObj.PasswordObfuscatorKey, authForm.Password)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
isSigninViaLdap := authForm.SigninMethod == "LDAP"
|
isSigninViaLdap := authForm.SigninMethod == "LDAP"
|
||||||
var isPasswordWithLdapEnabled bool
|
var isPasswordWithLdapEnabled bool
|
||||||
if authForm.SigninMethod == "Password" {
|
if authForm.SigninMethod == "Password" {
|
||||||
@ -665,6 +674,11 @@ func (c *ApiController) Login() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if application.IsSignupItemRequired("Invitation code") {
|
||||||
|
c.ResponseError(c.T("check:Invitation code cannot be blank"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Handle username conflicts
|
// Handle username conflicts
|
||||||
var tmpUser *object.User
|
var tmpUser *object.User
|
||||||
tmpUser, err = object.GetUser(util.GetId(application.Organization, userInfo.Username))
|
tmpUser, err = object.GetUser(util.GetId(application.Organization, userInfo.Username))
|
||||||
|
@ -16,6 +16,7 @@ package controllers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/beego/beego/utils/pagination"
|
"github.com/beego/beego/utils/pagination"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
@ -163,11 +164,17 @@ func (c *ApiController) GetPolicies() {
|
|||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if adapter == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf(c.T("the adapter: %s is not found"), adapterId))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
err = adapter.InitAdapter()
|
err = adapter.InitAdapter()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.ResponseOk()
|
c.ResponseOk()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -60,7 +60,6 @@ func (c *ApiController) Unlink() {
|
|||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if application == nil {
|
if application == nil {
|
||||||
c.ResponseError(c.T("link:You can't unlink yourself, you are not a member of any application"))
|
c.ResponseError(c.T("link:You can't unlink yourself, you are not a member of any application"))
|
||||||
return
|
return
|
||||||
|
@ -14,7 +14,11 @@
|
|||||||
|
|
||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import "github.com/casdoor/casdoor/object"
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
)
|
||||||
|
|
||||||
// GetOidcDiscovery
|
// GetOidcDiscovery
|
||||||
// @Title GetOidcDiscovery
|
// @Title GetOidcDiscovery
|
||||||
@ -42,3 +46,31 @@ func (c *RootController) GetJwks() {
|
|||||||
c.Data["json"] = jwks
|
c.Data["json"] = jwks
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetWebFinger
|
||||||
|
// @Title GetWebFinger
|
||||||
|
// @Tag OIDC API
|
||||||
|
// @Param resource query string true "resource"
|
||||||
|
// @Success 200 {object} object.WebFinger
|
||||||
|
// @router /.well-known/webfinger [get]
|
||||||
|
func (c *RootController) GetWebFinger() {
|
||||||
|
resource := c.Input().Get("resource")
|
||||||
|
rels := []string{}
|
||||||
|
host := c.Ctx.Request.Host
|
||||||
|
|
||||||
|
for key, value := range c.Input() {
|
||||||
|
if strings.HasPrefix(key, "rel") {
|
||||||
|
rels = append(rels, value...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
webfinger, err := object.GetWebFinger(resource, rels, host)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = webfinger
|
||||||
|
c.Ctx.Output.ContentType("application/jrd+json")
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
@ -65,7 +65,7 @@ func (c *ApiController) GetOrganizations() {
|
|||||||
c.ResponseOk(organizations)
|
c.ResponseOk(organizations)
|
||||||
} else {
|
} else {
|
||||||
limit := util.ParseInt(limit)
|
limit := util.ParseInt(limit)
|
||||||
count, err := object.GetOrganizationCount(owner, field, value)
|
count, err := object.GetOrganizationCount(owner, organizationName, field, value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
@ -138,7 +138,7 @@ func (c *ApiController) AddOrganization() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
count, err := object.GetOrganizationCount("", "", "")
|
count, err := object.GetOrganizationCount("", "", "", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
|
@ -257,7 +257,7 @@ func (c *ApiController) UploadResource() {
|
|||||||
fileType, _ = util.GetOwnerAndNameFromIdNoCheck(mimeType + "/")
|
fileType, _ = util.GetOwnerAndNameFromIdNoCheck(mimeType + "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
fullFilePath = object.GetTruncatedPath(provider, fullFilePath, 175)
|
fullFilePath = object.GetTruncatedPath(provider, fullFilePath, 450)
|
||||||
if tag != "avatar" && tag != "termsOfUse" && !strings.HasPrefix(tag, "idCard") {
|
if tag != "avatar" && tag != "termsOfUse" && !strings.HasPrefix(tag, "idCard") {
|
||||||
ext := filepath.Ext(filepath.Base(fullFilePath))
|
ext := filepath.Ext(filepath.Base(fullFilePath))
|
||||||
index := len(fullFilePath) - len(ext)
|
index := len(fullFilePath) - len(ext)
|
||||||
|
@ -46,10 +46,10 @@ func (c *ApiController) GetSystemInfo() {
|
|||||||
// @Success 200 {object} util.VersionInfo The Response object
|
// @Success 200 {object} util.VersionInfo The Response object
|
||||||
// @router /get-version-info [get]
|
// @router /get-version-info [get]
|
||||||
func (c *ApiController) GetVersionInfo() {
|
func (c *ApiController) GetVersionInfo() {
|
||||||
|
errInfo := ""
|
||||||
versionInfo, err := util.GetVersionInfo()
|
versionInfo, err := util.GetVersionInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
errInfo = "Git error: " + err.Error()
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if versionInfo.Version != "" {
|
if versionInfo.Version != "" {
|
||||||
@ -59,9 +59,11 @@ func (c *ApiController) GetVersionInfo() {
|
|||||||
|
|
||||||
versionInfo, err = util.GetVersionInfoFromFile()
|
versionInfo, err = util.GetVersionInfoFromFile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
errInfo = errInfo + ", File error: " + err.Error()
|
||||||
|
c.ResponseError(errInfo)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.ResponseOk(versionInfo)
|
c.ResponseOk(versionInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -333,6 +333,35 @@ func (c *ApiController) IntrospectToken() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if application.TokenFormat == "JWT-Standard" {
|
||||||
|
jwtToken, err := object.ParseStandardJwtTokenByApplication(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
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
jwtToken, err := object.ParseJwtTokenByApplication(tokenValue, application)
|
jwtToken, err := object.ParseJwtTokenByApplication(tokenValue, application)
|
||||||
if err != nil || jwtToken.Valid() != nil {
|
if err != nil || jwtToken.Valid() != nil {
|
||||||
// and token revoked case. but we not implement
|
// and token revoked case. but we not implement
|
||||||
|
@ -289,6 +289,16 @@ func (c *ApiController) UpdateUser() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if user.MfaEmailEnabled && user.Email == "" {
|
||||||
|
c.ResponseError(c.T("user:MFA email is enabled but email is empty"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.MfaPhoneEnabled && user.Phone == "" {
|
||||||
|
c.ResponseError(c.T("user:MFA phone is enabled but phone number is empty"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if msg := object.CheckUpdateUser(oldUser, &user, c.GetAcceptLanguage()); msg != "" {
|
if msg := object.CheckUpdateUser(oldUser, &user, c.GetAcceptLanguage()); msg != "" {
|
||||||
c.ResponseError(msg)
|
c.ResponseError(msg)
|
||||||
return
|
return
|
||||||
@ -400,6 +410,12 @@ func (c *ApiController) GetEmailAndPhone() {
|
|||||||
organization := c.Ctx.Request.Form.Get("organization")
|
organization := c.Ctx.Request.Form.Get("organization")
|
||||||
username := c.Ctx.Request.Form.Get("username")
|
username := c.Ctx.Request.Form.Get("username")
|
||||||
|
|
||||||
|
enableErrorMask2 := conf.GetConfigBool("enableErrorMask2")
|
||||||
|
if enableErrorMask2 {
|
||||||
|
c.ResponseError("Error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
user, err := object.GetUserByFields(organization, username)
|
user, err := object.GetUserByFields(organization, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
|
@ -45,6 +45,22 @@ func (c *ApiController) ResponseOk(data ...interface{}) {
|
|||||||
|
|
||||||
// ResponseError ...
|
// ResponseError ...
|
||||||
func (c *ApiController) ResponseError(error string, data ...interface{}) {
|
func (c *ApiController) ResponseError(error string, data ...interface{}) {
|
||||||
|
enableErrorMask2 := conf.GetConfigBool("enableErrorMask2")
|
||||||
|
if enableErrorMask2 {
|
||||||
|
error = c.T("subscription:Error")
|
||||||
|
|
||||||
|
resp := &Response{Status: "error", Msg: error}
|
||||||
|
c.ResponseJsonData(resp, data...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
enableErrorMask := conf.GetConfigBool("enableErrorMask")
|
||||||
|
if enableErrorMask {
|
||||||
|
if strings.HasPrefix(error, "The user: ") && strings.HasSuffix(error, " doesn't exist") || strings.HasPrefix(error, "用户: ") && strings.HasSuffix(error, "不存在") {
|
||||||
|
error = c.T("check:password or code is incorrect")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resp := &Response{Status: "error", Msg: error}
|
resp := &Response{Status: "error", Msg: error}
|
||||||
c.ResponseJsonData(resp, data...)
|
c.ResponseJsonData(resp, data...)
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,18 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func deployStaticFiles(provider *object.Provider) {
|
func deployStaticFiles(provider *object.Provider) {
|
||||||
storageProvider, err := storage.GetStorageProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.RegionId, provider.Bucket, provider.Endpoint)
|
certificate := ""
|
||||||
|
if provider.Category == "Storage" && provider.Type == "Casdoor" {
|
||||||
|
cert, err := object.GetCert(util.GetId(provider.Owner, provider.Cert))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if cert == nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
certificate = cert.Certificate
|
||||||
|
}
|
||||||
|
storageProvider, err := storage.GetStorageProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.RegionId, provider.Bucket, provider.Endpoint, certificate, provider.Content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,10 @@ type AuthForm struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
FirstName string `json:"firstName"`
|
FirstName string `json:"firstName"`
|
||||||
LastName string `json:"lastName"`
|
LastName string `json:"lastName"`
|
||||||
|
Gender string `json:"gender"`
|
||||||
|
Bio string `json:"bio"`
|
||||||
|
Tag string `json:"tag"`
|
||||||
|
Education string `json:"education"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Phone string `json:"phone"`
|
Phone string `json:"phone"`
|
||||||
Affiliation string `json:"affiliation"`
|
Affiliation string `json:"affiliation"`
|
||||||
|
4
go.mod
4
go.mod
@ -12,7 +12,7 @@ require (
|
|||||||
github.com/casdoor/go-sms-sender v0.24.0
|
github.com/casdoor/go-sms-sender v0.24.0
|
||||||
github.com/casdoor/gomail/v2 v2.0.1
|
github.com/casdoor/gomail/v2 v2.0.1
|
||||||
github.com/casdoor/notify v0.45.0
|
github.com/casdoor/notify v0.45.0
|
||||||
github.com/casdoor/oss v1.6.0
|
github.com/casdoor/oss v1.8.0
|
||||||
github.com/casdoor/xorm-adapter/v3 v3.1.0
|
github.com/casdoor/xorm-adapter/v3 v3.1.0
|
||||||
github.com/casvisor/casvisor-go-sdk v1.4.0
|
github.com/casvisor/casvisor-go-sdk v1.4.0
|
||||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||||
@ -30,7 +30,7 @@ require (
|
|||||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
|
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
|
||||||
github.com/go-webauthn/webauthn v0.6.0
|
github.com/go-webauthn/webauthn v0.6.0
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||||
github.com/google/uuid v1.4.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/json-iterator/go v1.1.12
|
github.com/json-iterator/go v1.1.12
|
||||||
github.com/lestrrat-go/jwx v1.2.29
|
github.com/lestrrat-go/jwx v1.2.29
|
||||||
github.com/lib/pq v1.10.9
|
github.com/lib/pq v1.10.9
|
||||||
|
9
go.sum
9
go.sum
@ -1083,6 +1083,8 @@ github.com/casbin/casbin/v2 v2.28.3/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRt
|
|||||||
github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
|
github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
|
||||||
github.com/casbin/casbin/v2 v2.77.2 h1:yQinn/w9x8AswiwqwtrXz93VU48R1aYTXdHEx4RI3jM=
|
github.com/casbin/casbin/v2 v2.77.2 h1:yQinn/w9x8AswiwqwtrXz93VU48R1aYTXdHEx4RI3jM=
|
||||||
github.com/casbin/casbin/v2 v2.77.2/go.mod h1:mzGx0hYW9/ksOSpw3wNjk3NRAroq5VMFYUQ6G43iGPk=
|
github.com/casbin/casbin/v2 v2.77.2/go.mod h1:mzGx0hYW9/ksOSpw3wNjk3NRAroq5VMFYUQ6G43iGPk=
|
||||||
|
github.com/casdoor/casdoor-go-sdk v0.50.0 h1:bUYbz/MzJuWfLKJbJM0+U0YpYewAur+THp5TKnufWZM=
|
||||||
|
github.com/casdoor/casdoor-go-sdk v0.50.0/go.mod h1:cMnkCQJgMYpgAlgEx8reSt1AVaDIQLcJ1zk5pzBaz+4=
|
||||||
github.com/casdoor/go-reddit/v2 v2.1.0 h1:kIbfdJ7AA7H0uTQ8s0q4GGZqSS5V9wVE74RrXyD9XPs=
|
github.com/casdoor/go-reddit/v2 v2.1.0 h1:kIbfdJ7AA7H0uTQ8s0q4GGZqSS5V9wVE74RrXyD9XPs=
|
||||||
github.com/casdoor/go-reddit/v2 v2.1.0/go.mod h1:eagkvwlZ4Hcsuc/uQsLHYEulz5jN65SVSwV/AIE7zsc=
|
github.com/casdoor/go-reddit/v2 v2.1.0/go.mod h1:eagkvwlZ4Hcsuc/uQsLHYEulz5jN65SVSwV/AIE7zsc=
|
||||||
github.com/casdoor/go-sms-sender v0.24.0 h1:LNLsce3EG/87I3JS6UiajF3LlQmdIiCgebEu0IE4wSM=
|
github.com/casdoor/go-sms-sender v0.24.0 h1:LNLsce3EG/87I3JS6UiajF3LlQmdIiCgebEu0IE4wSM=
|
||||||
@ -1091,8 +1093,8 @@ github.com/casdoor/gomail/v2 v2.0.1 h1:J+FG6x80s9e5lBHUn8Sv0Y56mud34KiWih5YdmudR
|
|||||||
github.com/casdoor/gomail/v2 v2.0.1/go.mod h1:VnGPslEAtpix5FjHisR/WKB1qvZDBaujbikxDe9d+2Q=
|
github.com/casdoor/gomail/v2 v2.0.1/go.mod h1:VnGPslEAtpix5FjHisR/WKB1qvZDBaujbikxDe9d+2Q=
|
||||||
github.com/casdoor/notify v0.45.0 h1:OlaFvcQFjGOgA4mRx07M8AH1gvb5xNo21mcqrVGlLgk=
|
github.com/casdoor/notify v0.45.0 h1:OlaFvcQFjGOgA4mRx07M8AH1gvb5xNo21mcqrVGlLgk=
|
||||||
github.com/casdoor/notify v0.45.0/go.mod h1:wNHQu0tiDROMBIvz0j3Om3Lhd5yZ+AIfnFb8MYb8OLQ=
|
github.com/casdoor/notify v0.45.0/go.mod h1:wNHQu0tiDROMBIvz0j3Om3Lhd5yZ+AIfnFb8MYb8OLQ=
|
||||||
github.com/casdoor/oss v1.6.0 h1:IOWrGLJ+VO82qS796eaRnzFPPA1Sn3cotYTi7O/VIlQ=
|
github.com/casdoor/oss v1.8.0 h1:uuyKhDIp7ydOtV4lpqhAY23Ban2Ln8La8+QT36CwylM=
|
||||||
github.com/casdoor/oss v1.6.0/go.mod h1:rJAWA0hLhtu94t6IRpotLUkXO1NWMASirywQYaGizJE=
|
github.com/casdoor/oss v1.8.0/go.mod h1:uaqO7KBI2lnZcnB8rF7O6C2bN7llIbfC5Ql8ex1yR1U=
|
||||||
github.com/casdoor/xorm-adapter/v3 v3.1.0 h1:NodWayRtSLVSeCvL9H3Hc61k0G17KhV9IymTCNfh3kk=
|
github.com/casdoor/xorm-adapter/v3 v3.1.0 h1:NodWayRtSLVSeCvL9H3Hc61k0G17KhV9IymTCNfh3kk=
|
||||||
github.com/casdoor/xorm-adapter/v3 v3.1.0/go.mod h1:4WTcUw+bTgBylGHeGHzTtBvuTXRS23dtwzFLl9tsgFM=
|
github.com/casdoor/xorm-adapter/v3 v3.1.0/go.mod h1:4WTcUw+bTgBylGHeGHzTtBvuTXRS23dtwzFLl9tsgFM=
|
||||||
github.com/casvisor/casvisor-go-sdk v1.4.0 h1:hbZEGGJ1cwdHFAxeXrMoNw6yha6Oyg2F0qQhBNCN/dg=
|
github.com/casvisor/casvisor-go-sdk v1.4.0 h1:hbZEGGJ1cwdHFAxeXrMoNw6yha6Oyg2F0qQhBNCN/dg=
|
||||||
@ -1460,8 +1462,9 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
|
|||||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
|
||||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
|
github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
|
github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg=
|
github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg=
|
||||||
|
@ -45,6 +45,8 @@ func TestGenerateI18nFrontend(t *testing.T) {
|
|||||||
applyToOtherLanguage("frontend", "uk", data)
|
applyToOtherLanguage("frontend", "uk", data)
|
||||||
applyToOtherLanguage("frontend", "kk", data)
|
applyToOtherLanguage("frontend", "kk", data)
|
||||||
applyToOtherLanguage("frontend", "fa", data)
|
applyToOtherLanguage("frontend", "fa", data)
|
||||||
|
applyToOtherLanguage("frontend", "cs", data)
|
||||||
|
applyToOtherLanguage("frontend", "sk", data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGenerateI18nBackend(t *testing.T) {
|
func TestGenerateI18nBackend(t *testing.T) {
|
||||||
@ -73,4 +75,6 @@ func TestGenerateI18nBackend(t *testing.T) {
|
|||||||
applyToOtherLanguage("backend", "uk", data)
|
applyToOtherLanguage("backend", "uk", data)
|
||||||
applyToOtherLanguage("backend", "kk", data)
|
applyToOtherLanguage("backend", "kk", data)
|
||||||
applyToOtherLanguage("backend", "fa", data)
|
applyToOtherLanguage("backend", "fa", data)
|
||||||
|
applyToOtherLanguage("backend", "cs", data)
|
||||||
|
applyToOtherLanguage("backend", "sk", data)
|
||||||
}
|
}
|
||||||
|
167
i18n/locales/cs/data.json
Normal file
167
i18n/locales/cs/data.json
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
{
|
||||||
|
"account": {
|
||||||
|
"Failed to add user": "Nepodařilo se přidat uživatele",
|
||||||
|
"Get init score failed, error: %w": "Nepodařilo se získat počáteční skóre, chyba: %w",
|
||||||
|
"Please sign out first": "Nejprve se prosím odhlaste",
|
||||||
|
"The application does not allow to sign up new account": "Aplikace neumožňuje registraci nového účtu"
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"Challenge method should be S256": "Metoda výzvy by měla být S256",
|
||||||
|
"Failed to create user, user information is invalid: %s": "Nepodařilo se vytvořit uživatele, informace o uživateli jsou neplatné: %s",
|
||||||
|
"Failed to login in: %s": "Nepodařilo se přihlásit: %s",
|
||||||
|
"Invalid token": "Neplatný token",
|
||||||
|
"State expected: %s, but got: %s": "Očekávaný stav: %s, ale získán: %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": "Účet pro poskytovatele: %s a uživatelské jméno: %s (%s) neexistuje a není povoleno se registrovat jako nový účet přes %%s, prosím použijte jiný způsob registrace",
|
||||||
|
"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": "Účet pro poskytovatele: %s a uživatelské jméno: %s (%s) neexistuje a není povoleno se registrovat jako nový účet, prosím kontaktujte svou IT podporu",
|
||||||
|
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "Účet pro poskytovatele: %s a uživatelské jméno: %s (%s) je již propojen s jiným účtem: %s (%s)",
|
||||||
|
"The application: %s does not exist": "Aplikace: %s neexistuje",
|
||||||
|
"The login method: login with LDAP is not enabled for the application": "Metoda přihlášení: přihlášení pomocí LDAP není pro aplikaci povolena",
|
||||||
|
"The login method: login with SMS is not enabled for the application": "Metoda přihlášení: přihlášení pomocí SMS není pro aplikaci povolena",
|
||||||
|
"The login method: login with email is not enabled for the application": "Metoda přihlášení: přihlášení pomocí emailu není pro aplikaci povolena",
|
||||||
|
"The login method: login with face is not enabled for the application": "Metoda přihlášení: přihlášení pomocí obličeje není pro aplikaci povolena",
|
||||||
|
"The login method: login with password is not enabled for the application": "Metoda přihlášení: přihlášení pomocí hesla není pro aplikaci povolena",
|
||||||
|
"The organization: %s does not exist": "Organizace: %s neexistuje",
|
||||||
|
"The provider: %s is not enabled for the application": "Poskytovatel: %s není pro aplikaci povolen",
|
||||||
|
"Unauthorized operation": "Neoprávněná operace",
|
||||||
|
"Unknown authentication type (not password or provider), form = %s": "Neznámý typ autentizace (není heslo nebo poskytovatel), formulář = %s",
|
||||||
|
"User's tag: %s is not listed in the application's tags": "Štítek uživatele: %s není uveden v štítcích aplikace",
|
||||||
|
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "Placený uživatel %s nemá aktivní nebo čekající předplatné a aplikace: %s nemá výchozí ceny"
|
||||||
|
},
|
||||||
|
"cas": {
|
||||||
|
"Service %s and %s do not match": "Služba %s a %s se neshodují"
|
||||||
|
},
|
||||||
|
"check": {
|
||||||
|
"Affiliation cannot be blank": "Příslušnost nemůže být prázdná",
|
||||||
|
"Default code does not match the code's matching rules": "Výchozí kód neodpovídá pravidlům pro shodu kódů",
|
||||||
|
"DisplayName cannot be blank": "Zobrazované jméno nemůže být prázdné",
|
||||||
|
"DisplayName is not valid real name": "Zobrazované jméno není platné skutečné jméno",
|
||||||
|
"Email already exists": "Email již existuje",
|
||||||
|
"Email cannot be empty": "Email nemůže být prázdný",
|
||||||
|
"Email is invalid": "Email je neplatný",
|
||||||
|
"Empty username.": "Prázdné uživatelské jméno.",
|
||||||
|
"Face data does not exist, cannot log in": "Data obličeje neexistují, nelze se přihlásit",
|
||||||
|
"Face data mismatch": "Neshoda dat obličeje",
|
||||||
|
"FirstName cannot be blank": "Křestní jméno nemůže být prázdné",
|
||||||
|
"Invitation code cannot be blank": "Pozvánkový kód nemůže být prázdný",
|
||||||
|
"Invitation code exhausted": "Pozvánkový kód vyčerpán",
|
||||||
|
"Invitation code is invalid": "Pozvánkový kód je neplatný",
|
||||||
|
"Invitation code suspended": "Pozvánkový kód pozastaven",
|
||||||
|
"LDAP user name or password incorrect": "Uživatelské jméno nebo heslo LDAP je nesprávné",
|
||||||
|
"LastName cannot be blank": "Příjmení nemůže být prázdné",
|
||||||
|
"Multiple accounts with same uid, please check your ldap server": "Více účtů se stejným uid, prosím zkontrolujte svůj ldap server",
|
||||||
|
"Organization does not exist": "Organizace neexistuje",
|
||||||
|
"Phone already exists": "Telefon již existuje",
|
||||||
|
"Phone cannot be empty": "Telefon nemůže být prázdný",
|
||||||
|
"Phone number is invalid": "Telefonní číslo je neplatné",
|
||||||
|
"Please register using the email corresponding to the invitation code": "Prosím zaregistrujte se pomocí emailu odpovídajícího pozvánkovému kódu",
|
||||||
|
"Please register using the phone corresponding to the invitation code": "Prosím zaregistrujte se pomocí telefonu odpovídajícího pozvánkovému kódu",
|
||||||
|
"Please register using the username corresponding to the invitation code": "Prosím zaregistrujte se pomocí uživatelského jména odpovídajícího pozvánkovému kódu",
|
||||||
|
"Session outdated, please login again": "Relace je zastaralá, prosím přihlaste se znovu",
|
||||||
|
"The invitation code has already been used": "Pozvánkový kód již byl použit",
|
||||||
|
"The user is forbidden to sign in, please contact the administrator": "Uživatel má zakázáno se přihlásit, prosím kontaktujte administrátora",
|
||||||
|
"The user: %s doesn't exist in LDAP server": "Uživatel: %s neexistuje na LDAP serveru",
|
||||||
|
"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.": "Uživatelské jméno může obsahovat pouze alfanumerické znaky, podtržítka nebo pomlčky, nemůže mít po sobě jdoucí pomlčky nebo podtržítka a nemůže začínat nebo končit pomlčkou nebo podtržítkem.",
|
||||||
|
"The value \\\"%s\\\" for account field \\\"%s\\\" doesn't match the account item regex": "Hodnota \\\"%s\\\" pro pole účtu \\\"%s\\\" neodpovídá regulárnímu výrazu položky účtu",
|
||||||
|
"The value \\\"%s\\\" for signup field \\\"%s\\\" doesn't match the signup item regex of the application \\\"%s\\\"": "Hodnota \\\"%s\\\" pro pole registrace \\\"%s\\\" neodpovídá regulárnímu výrazu položky registrace aplikace \\\"%s\\\"",
|
||||||
|
"Username already exists": "Uživatelské jméno již existuje",
|
||||||
|
"Username cannot be an email address": "Uživatelské jméno nemůže být emailová adresa",
|
||||||
|
"Username cannot contain white spaces": "Uživatelské jméno nemůže obsahovat mezery",
|
||||||
|
"Username cannot start with a digit": "Uživatelské jméno nemůže začínat číslicí",
|
||||||
|
"Username is too long (maximum is 39 characters).": "Uživatelské jméno je příliš dlouhé (maximálně 39 znaků).",
|
||||||
|
"Username must have at least 2 characters": "Uživatelské jméno musí mít alespoň 2 znaky",
|
||||||
|
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "Zadali jste špatné heslo nebo kód příliš mnohokrát, prosím počkejte %d minut a zkuste to znovu",
|
||||||
|
"Your region is not allow to signup by phone": "Vaše oblast neumožňuje registraci pomocí telefonu",
|
||||||
|
"password or code is incorrect": "heslo nebo kód je nesprávné",
|
||||||
|
"password or code is incorrect, you have %d remaining chances": "heslo nebo kód je nesprávné, máte %d zbývajících pokusů",
|
||||||
|
"unsupported password type: %s": "nepodporovaný typ hesla: %s"
|
||||||
|
},
|
||||||
|
"general": {
|
||||||
|
"Missing parameter": "Chybějící parametr",
|
||||||
|
"Please login first": "Prosím, přihlaste se nejprve",
|
||||||
|
"The organization: %s should have one application at least": "Organizace: %s by měla mít alespoň jednu aplikaci",
|
||||||
|
"The user: %s doesn't exist": "Uživatel: %s neexistuje",
|
||||||
|
"don't support captchaProvider: ": "nepodporuje captchaProvider: ",
|
||||||
|
"this operation is not allowed in demo mode": "tato operace není povolena v demo režimu",
|
||||||
|
"this operation requires administrator to perform": "tato operace vyžaduje administrátora"
|
||||||
|
},
|
||||||
|
"ldap": {
|
||||||
|
"Ldap server exist": "Ldap server existuje"
|
||||||
|
},
|
||||||
|
"link": {
|
||||||
|
"Please link first": "Prosím, nejprve propojte",
|
||||||
|
"This application has no providers": "Tato aplikace nemá žádné poskytovatele",
|
||||||
|
"This application has no providers of type": "Tato aplikace nemá žádné poskytovatele typu",
|
||||||
|
"This provider can't be unlinked": "Tento poskytovatel nemůže být odpojen",
|
||||||
|
"You are not the global admin, you can't unlink other users": "Nejste globální administrátor, nemůžete odpojovat jiné uživatele",
|
||||||
|
"You can't unlink yourself, you are not a member of any application": "Nemůžete odpojit sami sebe, nejste členem žádné aplikace"
|
||||||
|
},
|
||||||
|
"organization": {
|
||||||
|
"Only admin can modify the %s.": "Pouze administrátor může upravit %s.",
|
||||||
|
"The %s is immutable.": "%s je neměnný.",
|
||||||
|
"Unknown modify rule %s.": "Neznámé pravidlo úpravy %s."
|
||||||
|
},
|
||||||
|
"permission": {
|
||||||
|
"The permission: \\\"%s\\\" doesn't exist": "Oprávnění: \\\"%s\\\" neexistuje"
|
||||||
|
},
|
||||||
|
"provider": {
|
||||||
|
"Invalid application id": "Neplatné ID aplikace",
|
||||||
|
"the provider: %s does not exist": "poskytovatel: %s neexistuje"
|
||||||
|
},
|
||||||
|
"resource": {
|
||||||
|
"User is nil for tag: avatar": "Uživatel je nil pro tag: avatar",
|
||||||
|
"Username or fullFilePath is empty: username = %s, fullFilePath = %s": "Uživatelské jméno nebo úplná cesta k souboru je prázdná: uživatelské jméno = %s, úplná cesta k souboru = %s"
|
||||||
|
},
|
||||||
|
"saml": {
|
||||||
|
"Application %s not found": "Aplikace %s nebyla nalezena"
|
||||||
|
},
|
||||||
|
"saml_sp": {
|
||||||
|
"provider %s's category is not SAML": "poskytovatel %s není kategorie SAML"
|
||||||
|
},
|
||||||
|
"service": {
|
||||||
|
"Empty parameters for emailForm: %v": "Prázdné parametry pro emailForm: %v",
|
||||||
|
"Invalid Email receivers: %s": "Neplatní příjemci emailu: %s",
|
||||||
|
"Invalid phone receivers: %s": "Neplatní příjemci telefonu: %s"
|
||||||
|
},
|
||||||
|
"storage": {
|
||||||
|
"The objectKey: %s is not allowed": "objectKey: %s není povolen",
|
||||||
|
"The provider type: %s is not supported": "typ poskytovatele: %s není podporován"
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"Grant_type: %s is not supported in this application": "Grant_type: %s není v této aplikaci podporován",
|
||||||
|
"Invalid application or wrong clientSecret": "Neplatná aplikace nebo špatný clientSecret",
|
||||||
|
"Invalid client_id": "Neplatné client_id",
|
||||||
|
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Přesměrovací URI: %s neexistuje v seznamu povolených přesměrovacích URI",
|
||||||
|
"Token not found, invalid accessToken": "Token nenalezen, neplatný accessToken"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"Display name cannot be empty": "Zobrazované jméno nemůže být prázdné",
|
||||||
|
"New password cannot contain blank space.": "Nové heslo nemůže obsahovat prázdné místo."
|
||||||
|
},
|
||||||
|
"user_upload": {
|
||||||
|
"Failed to import users": "Nepodařilo se importovat uživatele"
|
||||||
|
},
|
||||||
|
"util": {
|
||||||
|
"No application is found for userId: %s": "Pro userId: %s nebyla nalezena žádná aplikace",
|
||||||
|
"No provider for category: %s is found for application: %s": "Pro kategorii: %s nebyl nalezen žádný poskytovatel pro aplikaci: %s",
|
||||||
|
"The provider: %s is not found": "Poskytovatel: %s nebyl nalezen"
|
||||||
|
},
|
||||||
|
"verification": {
|
||||||
|
"Invalid captcha provider.": "Neplatný poskytovatel captcha.",
|
||||||
|
"Phone number is invalid in your region %s": "Telefonní číslo je ve vaší oblasti %s neplatné",
|
||||||
|
"The verification code has not been sent yet!": "Ověřovací kód ještě nebyl odeslán!",
|
||||||
|
"The verification code has not been sent yet, or has already been used!": "Ověřovací kód ještě nebyl odeslán, nebo již byl použit!",
|
||||||
|
"Turing test failed.": "Turingův test selhal.",
|
||||||
|
"Unable to get the email modify rule.": "Nelze získat pravidlo pro úpravu emailu.",
|
||||||
|
"Unable to get the phone modify rule.": "Nelze získat pravidlo pro úpravu telefonu.",
|
||||||
|
"Unknown type": "Neznámý typ",
|
||||||
|
"Wrong verification code!": "Špatný ověřovací kód!",
|
||||||
|
"You should verify your code in %d min!": "Měli byste ověřit svůj kód do %d minut!",
|
||||||
|
"please add a SMS provider to the \\\"Providers\\\" list for the application: %s": "prosím přidejte poskytovatele SMS do seznamu \\\"Providers\\\" pro aplikaci: %s",
|
||||||
|
"please add an Email provider to the \\\"Providers\\\" list for the application: %s": "prosím přidejte poskytovatele emailu do seznamu \\\"Providers\\\" pro aplikaci: %s",
|
||||||
|
"the user does not exist, please sign up first": "uživatel neexistuje, prosím nejprve se zaregistrujte"
|
||||||
|
},
|
||||||
|
"webauthn": {
|
||||||
|
"Found no credentials for this user": "Nebyly nalezeny žádné přihlašovací údaje pro tohoto uživatele",
|
||||||
|
"Please call WebAuthnSigninBegin first": "Prosím, nejprve zavolejte WebAuthnSigninBegin"
|
||||||
|
}
|
||||||
|
}
|
167
i18n/locales/sk/data.json
Normal file
167
i18n/locales/sk/data.json
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
{
|
||||||
|
"account": {
|
||||||
|
"Failed to add user": "Nepodarilo sa pridať používateľa",
|
||||||
|
"Get init score failed, error: %w": "Získanie počiatočného skóre zlyhalo, chyba: %w",
|
||||||
|
"Please sign out first": "Najskôr sa prosím odhláste",
|
||||||
|
"The application does not allow to sign up new account": "Aplikácia neumožňuje registráciu nového účtu"
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"Challenge method should be S256": "Metóda výzvy by mala byť S256",
|
||||||
|
"Failed to create user, user information is invalid: %s": "Nepodarilo sa vytvoriť používateľa, informácie o používateľovi sú neplatné: %s",
|
||||||
|
"Failed to login in: %s": "Prihlásenie zlyhalo: %s",
|
||||||
|
"Invalid token": "Neplatný token",
|
||||||
|
"State expected: %s, but got: %s": "Očakávaný stav: %s, ale dostali sme: %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": "Účet pre poskytovateľa: %s a používateľské meno: %s (%s) neexistuje a nie je povolené zaregistrovať nový účet cez %%s, prosím použite iný spôsob registrácie",
|
||||||
|
"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": "Účet pre poskytovateľa: %s a používateľské meno: %s (%s) neexistuje a nie je povolené zaregistrovať nový účet, prosím kontaktujte vašu IT podporu",
|
||||||
|
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "Účet pre poskytovateľa: %s a používateľské meno: %s (%s) je už prepojený s iným účtom: %s (%s)",
|
||||||
|
"The application: %s does not exist": "Aplikácia: %s neexistuje",
|
||||||
|
"The login method: login with LDAP is not enabled for the application": "Metóda prihlásenia: prihlásenie pomocou LDAP nie je pre aplikáciu povolená",
|
||||||
|
"The login method: login with SMS is not enabled for the application": "Metóda prihlásenia: prihlásenie pomocou SMS nie je pre aplikáciu povolená",
|
||||||
|
"The login method: login with email is not enabled for the application": "Metóda prihlásenia: prihlásenie pomocou e-mailu nie je pre aplikáciu povolená",
|
||||||
|
"The login method: login with face is not enabled for the application": "Metóda prihlásenia: prihlásenie pomocou tváre nie je pre aplikáciu povolená",
|
||||||
|
"The login method: login with password is not enabled for the application": "Metóda prihlásenia: prihlásenie pomocou hesla nie je pre aplikáciu povolená",
|
||||||
|
"The organization: %s does not exist": "Organizácia: %s neexistuje",
|
||||||
|
"The provider: %s is not enabled for the application": "Poskytovateľ: %s nie je pre aplikáciu povolený",
|
||||||
|
"Unauthorized operation": "Neautorizovaná operácia",
|
||||||
|
"Unknown authentication type (not password or provider), form = %s": "Neznámy typ autentifikácie (nie heslo alebo poskytovateľ), forma = %s",
|
||||||
|
"User's tag: %s is not listed in the application's tags": "Štítok používateľa: %s nie je uvedený v štítkoch aplikácie",
|
||||||
|
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "platiaci používateľ %s nemá aktívne alebo čakajúce predplatné a aplikácia: %s nemá predvolenú cenovú politiku"
|
||||||
|
},
|
||||||
|
"cas": {
|
||||||
|
"Service %s and %s do not match": "Služba %s a %s sa nezhodujú"
|
||||||
|
},
|
||||||
|
"check": {
|
||||||
|
"Affiliation cannot be blank": "Príslušnosť nemôže byť prázdna",
|
||||||
|
"Default code does not match the code's matching rules": "Predvolený kód nezodpovedá pravidlám zodpovedania kódu",
|
||||||
|
"DisplayName cannot be blank": "Zobrazované meno nemôže byť prázdne",
|
||||||
|
"DisplayName is not valid real name": "Zobrazované meno nie je platné skutočné meno",
|
||||||
|
"Email already exists": "E-mail už existuje",
|
||||||
|
"Email cannot be empty": "E-mail nemôže byť prázdny",
|
||||||
|
"Email is invalid": "E-mail je neplatný",
|
||||||
|
"Empty username.": "Prázdne používateľské meno.",
|
||||||
|
"Face data does not exist, cannot log in": "Dáta o tvári neexistujú, nemožno sa prihlásiť",
|
||||||
|
"Face data mismatch": "Nesúlad dát o tvári",
|
||||||
|
"FirstName cannot be blank": "Meno nemôže byť prázdne",
|
||||||
|
"Invitation code cannot be blank": "Kód pozvania nemôže byť prázdny",
|
||||||
|
"Invitation code exhausted": "Kód pozvania bol vyčerpaný",
|
||||||
|
"Invitation code is invalid": "Kód pozvania je neplatný",
|
||||||
|
"Invitation code suspended": "Kód pozvania bol pozastavený",
|
||||||
|
"LDAP user name or password incorrect": "LDAP používateľské meno alebo heslo sú nesprávne",
|
||||||
|
"LastName cannot be blank": "Priezvisko nemôže byť prázdne",
|
||||||
|
"Multiple accounts with same uid, please check your ldap server": "Viacero účtov s rovnakým uid, skontrolujte svoj ldap server",
|
||||||
|
"Organization does not exist": "Organizácia neexistuje",
|
||||||
|
"Phone already exists": "Telefón už existuje",
|
||||||
|
"Phone cannot be empty": "Telefón nemôže byť prázdny",
|
||||||
|
"Phone number is invalid": "Telefónne číslo je neplatné",
|
||||||
|
"Please register using the email corresponding to the invitation code": "Prosím, zaregistrujte sa pomocou e-mailu zodpovedajúceho kódu pozvania",
|
||||||
|
"Please register using the phone corresponding to the invitation code": "Prosím, zaregistrujte sa pomocou telefónu zodpovedajúceho kódu pozvania",
|
||||||
|
"Please register using the username corresponding to the invitation code": "Prosím, zaregistrujte sa pomocou používateľského mena zodpovedajúceho kódu pozvania",
|
||||||
|
"Session outdated, please login again": "Relácia je zastaraná, prosím, prihláste sa znova",
|
||||||
|
"The invitation code has already been used": "Kód pozvania už bol použitý",
|
||||||
|
"The user is forbidden to sign in, please contact the administrator": "Používateľovi je zakázané prihlásenie, prosím, kontaktujte administrátora",
|
||||||
|
"The user: %s doesn't exist in LDAP server": "Používateľ: %s neexistuje na LDAP serveri",
|
||||||
|
"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.": "Používateľské meno môže obsahovať iba alfanumerické znaky, podtržníky alebo pomlčky, nemôže obsahovať po sebe idúce pomlčky alebo podtržníky a nemôže začínať alebo končiť pomlčkou alebo podtržníkom.",
|
||||||
|
"The value \\\"%s\\\" for account field \\\"%s\\\" doesn't match the account item regex": "Hodnota \\\"%s\\\" pre pole účtu \\\"%s\\\" nezodpovedá regulárnemu výrazu položky účtu",
|
||||||
|
"The value \\\"%s\\\" for signup field \\\"%s\\\" doesn't match the signup item regex of the application \\\"%s\\\"": "Hodnota \\\"%s\\\" pre pole registrácie \\\"%s\\\" nezodpovedá regulárnemu výrazu položky registrácie aplikácie \\\"%s\\\"",
|
||||||
|
"Username already exists": "Používateľské meno už existuje",
|
||||||
|
"Username cannot be an email address": "Používateľské meno nemôže byť e-mailová adresa",
|
||||||
|
"Username cannot contain white spaces": "Používateľské meno nemôže obsahovať medzery",
|
||||||
|
"Username cannot start with a digit": "Používateľské meno nemôže začínať číslicou",
|
||||||
|
"Username is too long (maximum is 39 characters).": "Používateľské meno je príliš dlhé (maximum je 39 znakov).",
|
||||||
|
"Username must have at least 2 characters": "Používateľské meno musí mať aspoň 2 znaky",
|
||||||
|
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "Zadali ste nesprávne heslo alebo kód príliš veľa krát, prosím, počkajte %d minút a skúste to znova",
|
||||||
|
"Your region is not allow to signup by phone": "Váš región neumožňuje registráciu cez telefón",
|
||||||
|
"password or code is incorrect": "heslo alebo kód je nesprávne",
|
||||||
|
"password or code is incorrect, you have %d remaining chances": "heslo alebo kód je nesprávne, máte %d zostávajúcich pokusov",
|
||||||
|
"unsupported password type: %s": "nepodporovaný typ hesla: %s"
|
||||||
|
},
|
||||||
|
"general": {
|
||||||
|
"Missing parameter": "Chýbajúci parameter",
|
||||||
|
"Please login first": "Najskôr sa prosím prihláste",
|
||||||
|
"The organization: %s should have one application at least": "Organizácia: %s by mala mať aspoň jednu aplikáciu",
|
||||||
|
"The user: %s doesn't exist": "Používateľ: %s neexistuje",
|
||||||
|
"don't support captchaProvider: ": "nepodporuje captchaProvider: ",
|
||||||
|
"this operation is not allowed in demo mode": "táto operácia nie je povolená v demo režime",
|
||||||
|
"this operation requires administrator to perform": "táto operácia vyžaduje vykonanie administrátorom"
|
||||||
|
},
|
||||||
|
"ldap": {
|
||||||
|
"Ldap server exist": "LDAP server existuje"
|
||||||
|
},
|
||||||
|
"link": {
|
||||||
|
"Please link first": "Najskôr sa prosím prepojte",
|
||||||
|
"This application has no providers": "Táto aplikácia nemá žiadnych poskytovateľov",
|
||||||
|
"This application has no providers of type": "Táto aplikácia nemá poskytovateľov typu",
|
||||||
|
"This provider can't be unlinked": "Tento poskytovateľ nemôže byť odpojený",
|
||||||
|
"You are not the global admin, you can't unlink other users": "Nie ste globálny administrátor, nemôžete odpojiť iných používateľov",
|
||||||
|
"You can't unlink yourself, you are not a member of any application": "Nemôžete sa odpojiť, nie ste členom žiadnej aplikácie"
|
||||||
|
},
|
||||||
|
"organization": {
|
||||||
|
"Only admin can modify the %s.": "Len administrátor môže upravovať %s.",
|
||||||
|
"The %s is immutable.": "%s je nemenný.",
|
||||||
|
"Unknown modify rule %s.": "Neznáme pravidlo úprav %s."
|
||||||
|
},
|
||||||
|
"permission": {
|
||||||
|
"The permission: \\\"%s\\\" doesn't exist": "Povolenie: \\\"%s\\\" neexistuje"
|
||||||
|
},
|
||||||
|
"provider": {
|
||||||
|
"Invalid application id": "Neplatné id aplikácie",
|
||||||
|
"the provider: %s does not exist": "poskytovateľ: %s neexistuje"
|
||||||
|
},
|
||||||
|
"resource": {
|
||||||
|
"User is nil for tag: avatar": "Používateľ je nil pre tag: avatar",
|
||||||
|
"Username or fullFilePath is empty: username = %s, fullFilePath = %s": "Používateľské meno alebo fullFilePath je prázdny: používateľské meno = %s, fullFilePath = %s"
|
||||||
|
},
|
||||||
|
"saml": {
|
||||||
|
"Application %s not found": "Aplikácia %s nebola nájdená"
|
||||||
|
},
|
||||||
|
"saml_sp": {
|
||||||
|
"provider %s's category is not SAML": "kategória poskytovateľa %s nie je SAML"
|
||||||
|
},
|
||||||
|
"service": {
|
||||||
|
"Empty parameters for emailForm: %v": "Prázdne parametre pre emailForm: %v",
|
||||||
|
"Invalid Email receivers: %s": "Neplatní príjemcovia e-mailu: %s",
|
||||||
|
"Invalid phone receivers: %s": "Neplatní príjemcovia telefónu: %s"
|
||||||
|
},
|
||||||
|
"storage": {
|
||||||
|
"The objectKey: %s is not allowed": "objectKey: %s nie je povolený",
|
||||||
|
"The provider type: %s is not supported": "Typ poskytovateľa: %s nie je podporovaný"
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"Grant_type: %s is not supported in this application": "Grant_type: %s nie je podporovaný v tejto aplikácii",
|
||||||
|
"Invalid application or wrong clientSecret": "Neplatná aplikácia alebo nesprávny clientSecret",
|
||||||
|
"Invalid client_id": "Neplatný client_id",
|
||||||
|
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s neexistuje v zozname povolených Redirect URI",
|
||||||
|
"Token not found, invalid accessToken": "Token nebol nájdený, neplatný accessToken"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"Display name cannot be empty": "Zobrazované meno nemôže byť prázdne",
|
||||||
|
"New password cannot contain blank space.": "Nové heslo nemôže obsahovať medzery."
|
||||||
|
},
|
||||||
|
"user_upload": {
|
||||||
|
"Failed to import users": "Nepodarilo sa importovať používateľov"
|
||||||
|
},
|
||||||
|
"util": {
|
||||||
|
"No application is found for userId: %s": "Nebola nájdená žiadna aplikácia pre userId: %s",
|
||||||
|
"No provider for category: %s is found for application: %s": "Pre aplikáciu: %s nebol nájdený žiadny poskytovateľ pre kategóriu: %s",
|
||||||
|
"The provider: %s is not found": "Poskytovateľ: %s nebol nájdený"
|
||||||
|
},
|
||||||
|
"verification": {
|
||||||
|
"Invalid captcha provider.": "Neplatný captcha poskytovateľ.",
|
||||||
|
"Phone number is invalid in your region %s": "Telefónne číslo je neplatné vo vašom regióne %s",
|
||||||
|
"The verification code has not been sent yet!": "Overovací kód ešte nebol odoslaný!",
|
||||||
|
"The verification code has not been sent yet, or has already been used!": "Overovací kód ešte nebol odoslaný, alebo bol už použitý!",
|
||||||
|
"Turing test failed.": "Test Turinga zlyhal.",
|
||||||
|
"Unable to get the email modify rule.": "Nepodarilo sa získať pravidlo úpravy e-mailu.",
|
||||||
|
"Unable to get the phone modify rule.": "Nepodarilo sa získať pravidlo úpravy telefónu.",
|
||||||
|
"Unknown type": "Neznámy typ",
|
||||||
|
"Wrong verification code!": "Nesprávny overovací kód!",
|
||||||
|
"You should verify your code in %d min!": "Overte svoj kód za %d minút!",
|
||||||
|
"please add a SMS provider to the \\\"Providers\\\" list for the application: %s": "prosím pridajte SMS poskytovateľa do zoznamu \\\"Poskytovatelia\\\" pre aplikáciu: %s",
|
||||||
|
"please add an Email provider to the \\\"Providers\\\" list for the application: %s": "prosím pridajte e-mailového poskytovateľa do zoznamu \\\"Poskytovatelia\\\" pre aplikáciu: %s",
|
||||||
|
"the user does not exist, please sign up first": "používateľ neexistuje, prosím, zaregistrujte sa najskôr"
|
||||||
|
},
|
||||||
|
"webauthn": {
|
||||||
|
"Found no credentials for this user": "Nenašli sa žiadne prihlasovacie údaje pre tohto používateľa",
|
||||||
|
"Please call WebAuthnSigninBegin first": "Najskôr prosím zavolajte WebAuthnSigninBegin"
|
||||||
|
}
|
||||||
|
}
|
@ -200,7 +200,7 @@ func (idp *AlipayIdProvider) postWithBody(body interface{}, targetUrl string) ([
|
|||||||
|
|
||||||
formData.Set("sign", sign)
|
formData.Set("sign", sign)
|
||||||
|
|
||||||
resp, err := idp.Client.PostForm(targetUrl, formData)
|
resp, err := idp.Client.Post(targetUrl, "application/x-www-form-urlencoded;charset=utf-8", strings.NewReader(formData.Encode()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
18
idp/lark.go
18
idp/lark.go
@ -22,6 +22,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/nyaruka/phonenumbers"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -199,12 +200,25 @@ func (idp *LarkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var phoneNumber string
|
||||||
|
var countryCode string
|
||||||
|
if len(larkUserInfo.Data.Mobile) != 0 {
|
||||||
|
phoneNumberParsed, err := phonenumbers.Parse(larkUserInfo.Data.Mobile, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
countryCode = phonenumbers.GetRegionCodeForNumber(phoneNumberParsed)
|
||||||
|
phoneNumber = fmt.Sprintf("%d", phoneNumberParsed.GetNationalNumber())
|
||||||
|
}
|
||||||
|
|
||||||
userInfo := UserInfo{
|
userInfo := UserInfo{
|
||||||
Id: larkUserInfo.Data.OpenId,
|
Id: larkUserInfo.Data.OpenId,
|
||||||
DisplayName: larkUserInfo.Data.EnName,
|
DisplayName: larkUserInfo.Data.Name,
|
||||||
Username: larkUserInfo.Data.Name,
|
Username: larkUserInfo.Data.UserId,
|
||||||
Email: larkUserInfo.Data.Email,
|
Email: larkUserInfo.Data.Email,
|
||||||
AvatarUrl: larkUserInfo.Data.AvatarUrl,
|
AvatarUrl: larkUserInfo.Data.AvatarUrl,
|
||||||
|
Phone: phoneNumber,
|
||||||
|
CountryCode: countryCode,
|
||||||
}
|
}
|
||||||
return &userInfo, nil
|
return &userInfo, nil
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,9 @@
|
|||||||
"FI",
|
"FI",
|
||||||
"SE",
|
"SE",
|
||||||
"UA",
|
"UA",
|
||||||
"KZ"
|
"KZ",
|
||||||
|
"CZ",
|
||||||
|
"SK"
|
||||||
],
|
],
|
||||||
"defaultAvatar": "",
|
"defaultAvatar": "",
|
||||||
"defaultApplication": "",
|
"defaultApplication": "",
|
||||||
@ -62,7 +64,9 @@
|
|||||||
"sv",
|
"sv",
|
||||||
"uk",
|
"uk",
|
||||||
"kk",
|
"kk",
|
||||||
"fa"
|
"fa",
|
||||||
|
"cs",
|
||||||
|
"sk"
|
||||||
],
|
],
|
||||||
"masterPassword": "",
|
"masterPassword": "",
|
||||||
"defaultPassword": "",
|
"defaultPassword": "",
|
||||||
|
@ -59,7 +59,15 @@ func handleBind(w ldap.ResponseWriter, m *ldap.Message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bindPassword := string(r.AuthenticationSimple())
|
bindPassword := string(r.AuthenticationSimple())
|
||||||
bindUser, err := object.CheckUserPassword(bindOrg, bindUsername, bindPassword, "en")
|
|
||||||
|
enableCaptcha := false
|
||||||
|
isSigninViaLdap := false
|
||||||
|
isPasswordWithLdapEnabled := false
|
||||||
|
if bindPassword != "" {
|
||||||
|
isPasswordWithLdapEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
bindUser, err := object.CheckUserPassword(bindOrg, bindUsername, bindPassword, "en", enableCaptcha, isSigninViaLdap, isPasswordWithLdapEnabled)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Bind failed User=%s, Pass=%#v, ErrMsg=%s", string(r.Name()), r.Authentication(), err)
|
log.Printf("Bind failed User=%s, Pass=%#v, ErrMsg=%s", string(r.Name()), r.Authentication(), err)
|
||||||
res.SetResultCode(ldap.LDAPResultInvalidCredentials)
|
res.SetResultCode(ldap.LDAPResultInvalidCredentials)
|
||||||
@ -122,6 +130,9 @@ func handleSearch(w ldap.ResponseWriter, m *ldap.Message) {
|
|||||||
e.AddAttribute("homeDirectory", message.AttributeValue("/home/"+user.Name))
|
e.AddAttribute("homeDirectory", message.AttributeValue("/home/"+user.Name))
|
||||||
e.AddAttribute("cn", message.AttributeValue(user.Name))
|
e.AddAttribute("cn", message.AttributeValue(user.Name))
|
||||||
e.AddAttribute("uid", message.AttributeValue(user.Id))
|
e.AddAttribute("uid", message.AttributeValue(user.Id))
|
||||||
|
for _, group := range user.Groups {
|
||||||
|
e.AddAttribute(ldapMemberOfAttr, message.AttributeValue(group))
|
||||||
|
}
|
||||||
attrs := r.Attributes()
|
attrs := r.Attributes()
|
||||||
for _, attr := range attrs {
|
for _, attr := range attrs {
|
||||||
if string(attr) == "*" {
|
if string(attr) == "*" {
|
||||||
|
21
ldap/util.go
21
ldap/util.go
@ -79,6 +79,8 @@ var ldapAttributesMapping = map[string]FieldRelation{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ldapMemberOfAttr = "memberOf"
|
||||||
|
|
||||||
var AdditionalLdapAttributes []message.LDAPString
|
var AdditionalLdapAttributes []message.LDAPString
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -180,7 +182,22 @@ func buildUserFilterCondition(filter interface{}) (builder.Cond, error) {
|
|||||||
}
|
}
|
||||||
return builder.Not{cond}, nil
|
return builder.Not{cond}, nil
|
||||||
case message.FilterEqualityMatch:
|
case message.FilterEqualityMatch:
|
||||||
field, err := getUserFieldFromAttribute(string(f.AttributeDesc()))
|
attr := string(f.AttributeDesc())
|
||||||
|
|
||||||
|
if attr == ldapMemberOfAttr {
|
||||||
|
groupId := string(f.AssertionValue())
|
||||||
|
users, err := object.GetGroupUsers(groupId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var names []string
|
||||||
|
for _, user := range users {
|
||||||
|
names = append(names, user.Name)
|
||||||
|
}
|
||||||
|
return builder.In("name", names), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
field, err := getUserFieldFromAttribute(attr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -246,7 +263,7 @@ func GetFilteredUsers(m *ldap.Message) (filteredUsers []*object.User, code int)
|
|||||||
return nil, code
|
return nil, code
|
||||||
}
|
}
|
||||||
|
|
||||||
if name == "*" && m.Client.IsOrgAdmin { // get all users from organization 'org'
|
if name == "*" { // get all users from organization 'org'
|
||||||
if m.Client.IsGlobalAdmin && org == "*" {
|
if m.Client.IsGlobalAdmin && org == "*" {
|
||||||
filteredUsers, err = object.GetGlobalUsersWithFilter(buildSafeCondition(r.Filter()))
|
filteredUsers, err = object.GetGlobalUsersWithFilter(buildSafeCondition(r.Filter()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
2
main.go
2
main.go
@ -56,6 +56,7 @@ func main() {
|
|||||||
beego.InsertFilter("*", beego.BeforeRouter, routers.StaticFilter)
|
beego.InsertFilter("*", beego.BeforeRouter, routers.StaticFilter)
|
||||||
beego.InsertFilter("*", beego.BeforeRouter, routers.AutoSigninFilter)
|
beego.InsertFilter("*", beego.BeforeRouter, routers.AutoSigninFilter)
|
||||||
beego.InsertFilter("*", beego.BeforeRouter, routers.CorsFilter)
|
beego.InsertFilter("*", beego.BeforeRouter, routers.CorsFilter)
|
||||||
|
beego.InsertFilter("*", beego.BeforeRouter, routers.TimeoutFilter)
|
||||||
beego.InsertFilter("*", beego.BeforeRouter, routers.ApiFilter)
|
beego.InsertFilter("*", beego.BeforeRouter, routers.ApiFilter)
|
||||||
beego.InsertFilter("*", beego.BeforeRouter, routers.PrometheusFilter)
|
beego.InsertFilter("*", beego.BeforeRouter, routers.PrometheusFilter)
|
||||||
beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage)
|
beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage)
|
||||||
@ -71,6 +72,7 @@ func main() {
|
|||||||
beego.BConfig.WebConfig.Session.SessionProviderConfig = conf.GetConfigString("redisEndpoint")
|
beego.BConfig.WebConfig.Session.SessionProviderConfig = conf.GetConfigString("redisEndpoint")
|
||||||
}
|
}
|
||||||
beego.BConfig.WebConfig.Session.SessionCookieLifeTime = 3600 * 24 * 30
|
beego.BConfig.WebConfig.Session.SessionCookieLifeTime = 3600 * 24 * 30
|
||||||
|
beego.BConfig.WebConfig.Session.SessionGCMaxLifetime = 3600 * 24 * 30
|
||||||
// beego.BConfig.WebConfig.Session.SessionCookieSameSite = http.SameSiteNoneMode
|
// beego.BConfig.WebConfig.Session.SessionCookieSameSite = http.SameSiteNoneMode
|
||||||
|
|
||||||
err := logs.SetLogger(logs.AdapterFile, conf.GetConfigString("logConfig"))
|
err := logs.SetLogger(logs.AdapterFile, conf.GetConfigString("logConfig"))
|
||||||
|
@ -31,15 +31,17 @@ type SigninMethod struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SignupItem struct {
|
type SignupItem struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Visible bool `json:"visible"`
|
Visible bool `json:"visible"`
|
||||||
Required bool `json:"required"`
|
Required bool `json:"required"`
|
||||||
Prompted bool `json:"prompted"`
|
Prompted bool `json:"prompted"`
|
||||||
CustomCss string `json:"customCss"`
|
Type string `json:"type"`
|
||||||
Label string `json:"label"`
|
CustomCss string `json:"customCss"`
|
||||||
Placeholder string `json:"placeholder"`
|
Label string `json:"label"`
|
||||||
Regex string `json:"regex"`
|
Placeholder string `json:"placeholder"`
|
||||||
Rule string `json:"rule"`
|
Options []string `json:"options"`
|
||||||
|
Regex string `json:"regex"`
|
||||||
|
Rule string `json:"rule"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SigninItem struct {
|
type SigninItem struct {
|
||||||
@ -78,24 +80,27 @@ type Application struct {
|
|||||||
EnableSamlCompress bool `json:"enableSamlCompress"`
|
EnableSamlCompress bool `json:"enableSamlCompress"`
|
||||||
EnableSamlC14n10 bool `json:"enableSamlC14n10"`
|
EnableSamlC14n10 bool `json:"enableSamlC14n10"`
|
||||||
EnableSamlPostBinding bool `json:"enableSamlPostBinding"`
|
EnableSamlPostBinding bool `json:"enableSamlPostBinding"`
|
||||||
|
UseEmailAsSamlNameId bool `json:"useEmailAsSamlNameId"`
|
||||||
EnableWebAuthn bool `json:"enableWebAuthn"`
|
EnableWebAuthn bool `json:"enableWebAuthn"`
|
||||||
EnableLinkWithEmail bool `json:"enableLinkWithEmail"`
|
EnableLinkWithEmail bool `json:"enableLinkWithEmail"`
|
||||||
OrgChoiceMode string `json:"orgChoiceMode"`
|
OrgChoiceMode string `json:"orgChoiceMode"`
|
||||||
SamlReplyUrl string `xorm:"varchar(100)" json:"samlReplyUrl"`
|
SamlReplyUrl string `xorm:"varchar(100)" json:"samlReplyUrl"`
|
||||||
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
|
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
|
||||||
SigninMethods []*SigninMethod `xorm:"varchar(2000)" json:"signinMethods"`
|
SigninMethods []*SigninMethod `xorm:"varchar(2000)" json:"signinMethods"`
|
||||||
SignupItems []*SignupItem `xorm:"varchar(2000)" json:"signupItems"`
|
SignupItems []*SignupItem `xorm:"varchar(3000)" json:"signupItems"`
|
||||||
SigninItems []*SigninItem `xorm:"mediumtext" json:"signinItems"`
|
SigninItems []*SigninItem `xorm:"mediumtext" json:"signinItems"`
|
||||||
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
|
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
|
||||||
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
|
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
|
||||||
CertPublicKey string `xorm:"-" json:"certPublicKey"`
|
CertPublicKey string `xorm:"-" json:"certPublicKey"`
|
||||||
Tags []string `xorm:"mediumtext" json:"tags"`
|
Tags []string `xorm:"mediumtext" json:"tags"`
|
||||||
SamlAttributes []*SamlItem `xorm:"varchar(1000)" json:"samlAttributes"`
|
SamlAttributes []*SamlItem `xorm:"varchar(1000)" json:"samlAttributes"`
|
||||||
|
IsShared bool `json:"isShared"`
|
||||||
|
|
||||||
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
||||||
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
|
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
|
||||||
RedirectUris []string `xorm:"varchar(1000)" json:"redirectUris"`
|
RedirectUris []string `xorm:"varchar(1000)" json:"redirectUris"`
|
||||||
TokenFormat string `xorm:"varchar(100)" json:"tokenFormat"`
|
TokenFormat string `xorm:"varchar(100)" json:"tokenFormat"`
|
||||||
|
TokenSigningMethod string `xorm:"varchar(100)" json:"tokenSigningMethod"`
|
||||||
TokenFields []string `xorm:"varchar(1000)" json:"tokenFields"`
|
TokenFields []string `xorm:"varchar(1000)" json:"tokenFields"`
|
||||||
ExpireInHours int `json:"expireInHours"`
|
ExpireInHours int `json:"expireInHours"`
|
||||||
RefreshExpireInHours int `json:"refreshExpireInHours"`
|
RefreshExpireInHours int `json:"refreshExpireInHours"`
|
||||||
@ -123,9 +128,9 @@ func GetApplicationCount(owner, field, value string) (int64, error) {
|
|||||||
return session.Count(&Application{})
|
return session.Count(&Application{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetOrganizationApplicationCount(owner, Organization, field, value string) (int64, error) {
|
func GetOrganizationApplicationCount(owner, organization, field, value string) (int64, error) {
|
||||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||||
return session.Count(&Application{Organization: Organization})
|
return session.Where("organization = ? or is_shared = ? ", organization, true).Count(&Application{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetApplications(owner string) ([]*Application, error) {
|
func GetApplications(owner string) ([]*Application, error) {
|
||||||
@ -140,7 +145,7 @@ func GetApplications(owner string) ([]*Application, error) {
|
|||||||
|
|
||||||
func GetOrganizationApplications(owner string, organization string) ([]*Application, error) {
|
func GetOrganizationApplications(owner string, organization string) ([]*Application, error) {
|
||||||
applications := []*Application{}
|
applications := []*Application{}
|
||||||
err := ormer.Engine.Desc("created_time").Find(&applications, &Application{Organization: organization})
|
err := ormer.Engine.Desc("created_time").Where("organization = ? or is_shared = ? ", organization, true).Find(&applications, &Application{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return applications, err
|
return applications, err
|
||||||
}
|
}
|
||||||
@ -162,7 +167,7 @@ func GetPaginationApplications(owner string, offset, limit int, field, value, so
|
|||||||
func GetPaginationOrganizationApplications(owner, organization string, offset, limit int, field, value, sortField, sortOrder string) ([]*Application, error) {
|
func GetPaginationOrganizationApplications(owner, organization string, offset, limit int, field, value, sortField, sortOrder string) ([]*Application, error) {
|
||||||
applications := []*Application{}
|
applications := []*Application{}
|
||||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||||
err := session.Find(&applications, &Application{Organization: organization})
|
err := session.Where("organization = ? or is_shared = ? ", organization, true).Find(&applications, &Application{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return applications, err
|
return applications, err
|
||||||
}
|
}
|
||||||
@ -337,12 +342,18 @@ func getApplication(owner string, name string) (*Application, error) {
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
application := Application{Owner: owner, Name: name}
|
realApplicationName, sharedOrg := util.GetSharedOrgFromApp(name)
|
||||||
|
|
||||||
|
application := Application{Owner: owner, Name: realApplicationName}
|
||||||
existed, err := ormer.Engine.Get(&application)
|
existed, err := ormer.Engine.Get(&application)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if application.IsShared && sharedOrg != "" {
|
||||||
|
application.Organization = sharedOrg
|
||||||
|
}
|
||||||
|
|
||||||
if existed {
|
if existed {
|
||||||
err = extendApplicationWithProviders(&application)
|
err = extendApplicationWithProviders(&application)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -428,11 +439,18 @@ func GetApplicationByUserId(userId string) (application *Application, err error)
|
|||||||
|
|
||||||
func GetApplicationByClientId(clientId string) (*Application, error) {
|
func GetApplicationByClientId(clientId string) (*Application, error) {
|
||||||
application := Application{}
|
application := Application{}
|
||||||
existed, err := ormer.Engine.Where("client_id=?", clientId).Get(&application)
|
|
||||||
|
realClientId, sharedOrg := util.GetSharedOrgFromApp(clientId)
|
||||||
|
|
||||||
|
existed, err := ormer.Engine.Where("client_id=?", realClientId).Get(&application)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if application.IsShared && sharedOrg != "" {
|
||||||
|
application.Organization = sharedOrg
|
||||||
|
}
|
||||||
|
|
||||||
if existed {
|
if existed {
|
||||||
err = extendApplicationWithProviders(&application)
|
err = extendApplicationWithProviders(&application)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -516,7 +534,7 @@ func GetMaskedApplication(application *Application, userId string) *Application
|
|||||||
|
|
||||||
providerItems := []*ProviderItem{}
|
providerItems := []*ProviderItem{}
|
||||||
for _, providerItem := range application.Providers {
|
for _, providerItem := range application.Providers {
|
||||||
if providerItem.Provider != nil && (providerItem.Provider.Category == "OAuth" || providerItem.Provider.Category == "Web3" || providerItem.Provider.Category == "Captcha") {
|
if providerItem.Provider != nil && (providerItem.Provider.Category == "OAuth" || providerItem.Provider.Category == "Web3" || providerItem.Provider.Category == "Captcha" || providerItem.Provider.Category == "SAML") {
|
||||||
providerItems = append(providerItems, providerItem)
|
providerItems = append(providerItems, providerItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -626,6 +644,10 @@ func UpdateApplication(id string, application *Application) (bool, error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if application.IsShared == true && application.Organization != "built-in" {
|
||||||
|
return false, fmt.Errorf("only applications belonging to built-in organization can be shared")
|
||||||
|
}
|
||||||
|
|
||||||
for _, providerItem := range application.Providers {
|
for _, providerItem := range application.Providers {
|
||||||
providerItem.Provider = nil
|
providerItem.Provider = nil
|
||||||
}
|
}
|
||||||
|
@ -52,6 +52,9 @@ func GetFailedSigninConfigByUser(user *User) (int, int, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, err
|
return 0, 0, err
|
||||||
}
|
}
|
||||||
|
if application == nil {
|
||||||
|
return 0, 0, fmt.Errorf("the application for user %s is not found", user.GetId())
|
||||||
|
}
|
||||||
|
|
||||||
failedSigninLimit := application.FailedSigninLimit
|
failedSigninLimit := application.FailedSigninLimit
|
||||||
if failedSigninLimit == 0 {
|
if failedSigninLimit == 0 {
|
||||||
|
@ -78,6 +78,7 @@ func getBuiltInAccountItems() []*AccountItem {
|
|||||||
{Name: "Multi-factor authentication", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
{Name: "Multi-factor authentication", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||||
{Name: "WebAuthn credentials", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
{Name: "WebAuthn credentials", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||||
{Name: "Managed accounts", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
{Name: "Managed accounts", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||||
|
{Name: "MFA accounts", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,6 +110,7 @@ func initBuiltInOrganization() bool {
|
|||||||
EnableSoftDeletion: false,
|
EnableSoftDeletion: false,
|
||||||
IsProfilePublic: false,
|
IsProfilePublic: false,
|
||||||
UseEmailAsUsername: false,
|
UseEmailAsUsername: false,
|
||||||
|
EnableTour: true,
|
||||||
}
|
}
|
||||||
_, err = AddOrganization(organization)
|
_, err = AddOrganization(organization)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -182,6 +182,9 @@ func readInitDataFromFile(filePath string) (*InitData, error) {
|
|||||||
if organization.Tags == nil {
|
if organization.Tags == nil {
|
||||||
organization.Tags = []string{}
|
organization.Tags = []string{}
|
||||||
}
|
}
|
||||||
|
if organization.AccountItems == nil {
|
||||||
|
organization.AccountItems = []*AccountItem{}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for _, application := range data.Applications {
|
for _, application := range data.Applications {
|
||||||
if application.Providers == nil {
|
if application.Providers == nil {
|
||||||
@ -275,7 +278,9 @@ func initDefinedOrganization(organization *Organization) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
organization.CreatedTime = util.GetCurrentTime()
|
organization.CreatedTime = util.GetCurrentTime()
|
||||||
organization.AccountItems = getBuiltInAccountItems()
|
if len(organization.AccountItems) == 0 {
|
||||||
|
organization.AccountItems = getBuiltInAccountItems()
|
||||||
|
}
|
||||||
|
|
||||||
_, err = AddOrganization(organization)
|
_, err = AddOrganization(organization)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -32,6 +32,7 @@ type Ldap struct {
|
|||||||
BaseDn string `xorm:"varchar(100)" json:"baseDn"`
|
BaseDn string `xorm:"varchar(100)" json:"baseDn"`
|
||||||
Filter string `xorm:"varchar(200)" json:"filter"`
|
Filter string `xorm:"varchar(200)" json:"filter"`
|
||||||
FilterFields []string `xorm:"varchar(100)" json:"filterFields"`
|
FilterFields []string `xorm:"varchar(100)" json:"filterFields"`
|
||||||
|
DefaultGroup string `xorm:"varchar(100)" json:"defaultGroup"`
|
||||||
|
|
||||||
AutoSync int `json:"autoSync"`
|
AutoSync int `json:"autoSync"`
|
||||||
LastSync string `xorm:"varchar(100)" json:"lastSync"`
|
LastSync string `xorm:"varchar(100)" json:"lastSync"`
|
||||||
@ -148,7 +149,7 @@ func UpdateLdap(ldap *Ldap) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
affected, err := ormer.Engine.ID(ldap.Id).Cols("owner", "server_name", "host",
|
affected, err := ormer.Engine.ID(ldap.Id).Cols("owner", "server_name", "host",
|
||||||
"port", "enable_ssl", "username", "password", "base_dn", "filter", "filter_fields", "auto_sync").Update(ldap)
|
"port", "enable_ssl", "username", "password", "base_dn", "filter", "filter_fields", "auto_sync", "default_group").Update(ldap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
@ -339,6 +339,10 @@ func SyncLdapUsers(owner string, syncUsers []LdapUser, ldapId string) (existUser
|
|||||||
Ldap: syncUser.Uuid,
|
Ldap: syncUser.Uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ldap.DefaultGroup != "" {
|
||||||
|
newUser.Groups = []string{ldap.DefaultGroup}
|
||||||
|
}
|
||||||
|
|
||||||
affected, err := AddUser(newUser)
|
affected, err := AddUser(newUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
@ -44,6 +44,18 @@ type OidcDiscovery struct {
|
|||||||
EndSessionEndpoint string `json:"end_session_endpoint"`
|
EndSessionEndpoint string `json:"end_session_endpoint"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WebFinger struct {
|
||||||
|
Subject string `json:"subject"`
|
||||||
|
Links []WebFingerLink `json:"links"`
|
||||||
|
Aliases *[]string `json:"aliases,omitempty"`
|
||||||
|
Properties *map[string]string `json:"properties,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebFingerLink struct {
|
||||||
|
Rel string `json:"rel"`
|
||||||
|
Href string `json:"href"`
|
||||||
|
}
|
||||||
|
|
||||||
func isIpAddress(host string) bool {
|
func isIpAddress(host string) bool {
|
||||||
// Attempt to split the host and port, ignoring the error
|
// Attempt to split the host and port, ignoring the error
|
||||||
hostWithoutPort, _, err := net.SplitHostPort(host)
|
hostWithoutPort, _, err := net.SplitHostPort(host)
|
||||||
@ -112,7 +124,7 @@ func GetOidcDiscovery(host string) OidcDiscovery {
|
|||||||
ResponseModesSupported: []string{"query", "fragment", "login", "code", "link"},
|
ResponseModesSupported: []string{"query", "fragment", "login", "code", "link"},
|
||||||
GrantTypesSupported: []string{"password", "authorization_code"},
|
GrantTypesSupported: []string{"password", "authorization_code"},
|
||||||
SubjectTypesSupported: []string{"public"},
|
SubjectTypesSupported: []string{"public"},
|
||||||
IdTokenSigningAlgValuesSupported: []string{"RS256"},
|
IdTokenSigningAlgValuesSupported: []string{"RS256", "RS512", "ES256", "ES384", "ES512"},
|
||||||
ScopesSupported: []string{"openid", "email", "profile", "address", "phone", "offline_access"},
|
ScopesSupported: []string{"openid", "email", "profile", "address", "phone", "offline_access"},
|
||||||
ClaimsSupported: []string{"iss", "ver", "sub", "aud", "iat", "exp", "id", "type", "displayName", "avatar", "permanentAvatar", "email", "phone", "location", "affiliation", "title", "homepage", "bio", "tag", "region", "language", "score", "ranking", "isOnline", "isAdmin", "isForbidden", "signupApplication", "ldap"},
|
ClaimsSupported: []string{"iss", "ver", "sub", "aud", "iat", "exp", "id", "type", "displayName", "avatar", "permanentAvatar", "email", "phone", "location", "affiliation", "title", "homepage", "bio", "tag", "region", "language", "score", "ranking", "isOnline", "isAdmin", "isForbidden", "signupApplication", "ldap"},
|
||||||
RequestParameterSupported: true,
|
RequestParameterSupported: true,
|
||||||
@ -160,3 +172,43 @@ func GetJsonWebKeySet() (jose.JSONWebKeySet, error) {
|
|||||||
|
|
||||||
return jwks, nil
|
return jwks, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetWebFinger(resource string, rels []string, host string) (WebFinger, error) {
|
||||||
|
wf := WebFinger{}
|
||||||
|
|
||||||
|
resourceSplit := strings.Split(resource, ":")
|
||||||
|
|
||||||
|
if len(resourceSplit) != 2 {
|
||||||
|
return wf, fmt.Errorf("invalid resource")
|
||||||
|
}
|
||||||
|
|
||||||
|
resourceType := resourceSplit[0]
|
||||||
|
resourceValue := resourceSplit[1]
|
||||||
|
|
||||||
|
oidcDiscovery := GetOidcDiscovery(host)
|
||||||
|
|
||||||
|
switch resourceType {
|
||||||
|
case "acct":
|
||||||
|
user, err := GetUserByEmailOnly(resourceValue)
|
||||||
|
if err != nil {
|
||||||
|
return wf, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if user == nil {
|
||||||
|
return wf, fmt.Errorf("user not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
wf.Subject = resource
|
||||||
|
|
||||||
|
for _, rel := range rels {
|
||||||
|
if rel == "http://openid.net/specs/connect/1.0/issuer" {
|
||||||
|
wf.Links = append(wf.Links, WebFingerLink{
|
||||||
|
Rel: "http://openid.net/specs/connect/1.0/issuer",
|
||||||
|
Href: oidcDiscovery.Issuer,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return wf, nil
|
||||||
|
}
|
||||||
|
@ -56,10 +56,12 @@ type Organization struct {
|
|||||||
WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"`
|
WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"`
|
||||||
Logo string `xorm:"varchar(200)" json:"logo"`
|
Logo string `xorm:"varchar(200)" json:"logo"`
|
||||||
LogoDark string `xorm:"varchar(200)" json:"logoDark"`
|
LogoDark string `xorm:"varchar(200)" json:"logoDark"`
|
||||||
Favicon string `xorm:"varchar(100)" json:"favicon"`
|
Favicon string `xorm:"varchar(200)" json:"favicon"`
|
||||||
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
|
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
|
||||||
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
||||||
PasswordOptions []string `xorm:"varchar(100)" json:"passwordOptions"`
|
PasswordOptions []string `xorm:"varchar(100)" json:"passwordOptions"`
|
||||||
|
PasswordObfuscatorType string `xorm:"varchar(100)" json:"passwordObfuscatorType"`
|
||||||
|
PasswordObfuscatorKey string `xorm:"varchar(100)" json:"passwordObfuscatorKey"`
|
||||||
CountryCodes []string `xorm:"varchar(200)" json:"countryCodes"`
|
CountryCodes []string `xorm:"varchar(200)" json:"countryCodes"`
|
||||||
DefaultAvatar string `xorm:"varchar(200)" json:"defaultAvatar"`
|
DefaultAvatar string `xorm:"varchar(200)" json:"defaultAvatar"`
|
||||||
DefaultApplication string `xorm:"varchar(100)" json:"defaultApplication"`
|
DefaultApplication string `xorm:"varchar(100)" json:"defaultApplication"`
|
||||||
@ -73,14 +75,15 @@ type Organization struct {
|
|||||||
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
||||||
IsProfilePublic bool `json:"isProfilePublic"`
|
IsProfilePublic bool `json:"isProfilePublic"`
|
||||||
UseEmailAsUsername bool `json:"useEmailAsUsername"`
|
UseEmailAsUsername bool `json:"useEmailAsUsername"`
|
||||||
|
EnableTour bool `json:"enableTour"`
|
||||||
|
|
||||||
MfaItems []*MfaItem `xorm:"varchar(300)" json:"mfaItems"`
|
MfaItems []*MfaItem `xorm:"varchar(300)" json:"mfaItems"`
|
||||||
AccountItems []*AccountItem `xorm:"varchar(5000)" json:"accountItems"`
|
AccountItems []*AccountItem `xorm:"varchar(5000)" json:"accountItems"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetOrganizationCount(owner, field, value string) (int64, error) {
|
func GetOrganizationCount(owner, name, field, value string) (int64, error) {
|
||||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||||
return session.Count(&Organization{})
|
return session.Count(&Organization{Name: name})
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetOrganizations(owner string, name ...string) ([]*Organization, error) {
|
func GetOrganizations(owner string, name ...string) ([]*Organization, error) {
|
||||||
@ -318,6 +321,7 @@ func GetDefaultApplication(id string) (*Application, error) {
|
|||||||
if defaultApplication == nil {
|
if defaultApplication == nil {
|
||||||
return nil, fmt.Errorf("The default application: %s does not exist", organization.DefaultApplication)
|
return nil, fmt.Errorf("The default application: %s does not exist", organization.DefaultApplication)
|
||||||
} else {
|
} else {
|
||||||
|
defaultApplication.Organization = organization.Name
|
||||||
return defaultApplication, nil
|
return defaultApplication, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -355,6 +359,11 @@ func GetDefaultApplication(id string) (*Application, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = extendApplicationWithSigninMethods(defaultApplication)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return defaultApplication, nil
|
return defaultApplication, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,7 +201,7 @@ func notifyPayment(body []byte, owner string, paymentName string) (*Payment, *pp
|
|||||||
}
|
}
|
||||||
|
|
||||||
if payment.IsRecharge {
|
if payment.IsRecharge {
|
||||||
err = updateUserBalance(payment.Owner, payment.User, payment.Price)
|
err = UpdateUserBalance(payment.Owner, payment.User, payment.Price)
|
||||||
return payment, notifyResult, err
|
return payment, notifyResult, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,6 +222,19 @@ func NotifyPayment(body []byte, owner string, paymentName string) (*Payment, err
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transaction, err := GetTransaction(payment.GetId())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if transaction != nil {
|
||||||
|
transaction.State = payment.State
|
||||||
|
_, err = UpdateTransaction(transaction.GetId(), transaction)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return payment, nil
|
return payment, nil
|
||||||
|
@ -181,15 +181,15 @@ func UpdatePermission(id string, permission *Permission) (bool, error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldPermission.Adapter != "" && oldPermission.Adapter != permission.Adapter {
|
// if oldPermission.Adapter != "" && oldPermission.Adapter != permission.Adapter {
|
||||||
isEmpty, _ := ormer.Engine.IsTableEmpty(oldPermission.Adapter)
|
// isEmpty, _ := ormer.Engine.IsTableEmpty(oldPermission.Adapter)
|
||||||
if isEmpty {
|
// if isEmpty {
|
||||||
err = ormer.Engine.DropTables(oldPermission.Adapter)
|
// err = ormer.Engine.DropTables(oldPermission.Adapter)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return false, err
|
// return false, err
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
err = addGroupingPolicies(permission)
|
err = addGroupingPolicies(permission)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -312,15 +312,15 @@ func DeletePermission(permission *Permission) (bool, error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if permission.Adapter != "" && permission.Adapter != "permission_rule" {
|
// if permission.Adapter != "" && permission.Adapter != "permission_rule" {
|
||||||
isEmpty, _ := ormer.Engine.IsTableEmpty(permission.Adapter)
|
// isEmpty, _ := ormer.Engine.IsTableEmpty(permission.Adapter)
|
||||||
if isEmpty {
|
// if isEmpty {
|
||||||
err = ormer.Engine.DropTables(permission.Adapter)
|
// err = ormer.Engine.DropTables(permission.Adapter)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return false, err
|
// return false, err
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
return affected, nil
|
return affected, nil
|
||||||
|
@ -227,13 +227,17 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
|
|||||||
NotifyUrl: notifyUrl,
|
NotifyUrl: notifyUrl,
|
||||||
PaymentEnv: paymentEnv,
|
PaymentEnv: paymentEnv,
|
||||||
}
|
}
|
||||||
|
|
||||||
// custom process for WeChat & WeChat Pay
|
// custom process for WeChat & WeChat Pay
|
||||||
if provider.Type == "WeChat Pay" {
|
if provider.Type == "WeChat Pay" {
|
||||||
payReq.PayerId, err = getUserExtraProperty(user, "WeChat", idp.BuildWechatOpenIdKey(provider.ClientId2))
|
payReq.PayerId, err = getUserExtraProperty(user, "WeChat", idp.BuildWechatOpenIdKey(provider.ClientId2))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
} else if provider.Type == "Balance" {
|
||||||
|
payReq.PayerId = user.GetId()
|
||||||
}
|
}
|
||||||
|
|
||||||
payResp, err := pProvider.Pay(payReq)
|
payResp, err := pProvider.Pay(payReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
@ -264,12 +268,46 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
|
|||||||
OutOrderId: payResp.OrderId,
|
OutOrderId: payResp.OrderId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transaction := &Transaction{
|
||||||
|
Owner: payment.Owner,
|
||||||
|
Name: payment.Name,
|
||||||
|
DisplayName: payment.DisplayName,
|
||||||
|
Provider: provider.Name,
|
||||||
|
Category: provider.Category,
|
||||||
|
Type: provider.Type,
|
||||||
|
|
||||||
|
ProductName: product.Name,
|
||||||
|
ProductDisplayName: product.DisplayName,
|
||||||
|
Detail: product.Detail,
|
||||||
|
Tag: product.Tag,
|
||||||
|
Currency: product.Currency,
|
||||||
|
Amount: payment.Price,
|
||||||
|
ReturnUrl: payment.ReturnUrl,
|
||||||
|
|
||||||
|
User: payment.User,
|
||||||
|
Application: owner,
|
||||||
|
Payment: payment.GetId(),
|
||||||
|
|
||||||
|
State: pp.PaymentStateCreated,
|
||||||
|
}
|
||||||
|
|
||||||
if provider.Type == "Dummy" {
|
if provider.Type == "Dummy" {
|
||||||
payment.State = pp.PaymentStatePaid
|
payment.State = pp.PaymentStatePaid
|
||||||
err = updateUserBalance(user.Owner, user.Name, payment.Price)
|
err = UpdateUserBalance(user.Owner, user.Name, payment.Price)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
} else if provider.Type == "Balance" {
|
||||||
|
if product.Price > user.Balance {
|
||||||
|
return nil, nil, fmt.Errorf("insufficient user balance")
|
||||||
|
}
|
||||||
|
transaction.Amount = -transaction.Amount
|
||||||
|
err = UpdateUserBalance(user.Owner, user.Name, -product.Price)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
payment.State = pp.PaymentStatePaid
|
||||||
|
transaction.State = pp.PaymentStatePaid
|
||||||
}
|
}
|
||||||
|
|
||||||
affected, err := AddPayment(payment)
|
affected, err := AddPayment(payment)
|
||||||
@ -280,6 +318,17 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
|
|||||||
if !affected {
|
if !affected {
|
||||||
return nil, nil, fmt.Errorf("failed to add payment: %s", util.StructToJson(payment))
|
return nil, nil, fmt.Errorf("failed to add payment: %s", util.StructToJson(payment))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if product.IsRecharge || provider.Type == "Balance" {
|
||||||
|
affected, err = AddTransaction(transaction)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if !affected {
|
||||||
|
return nil, nil, fmt.Errorf("failed to add transaction: %s", util.StructToJson(payment))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return payment, payResp.AttachInfo, nil
|
return payment, payResp.AttachInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -309,6 +309,12 @@ func GetPaymentProvider(p *Provider) (pp.PaymentProvider, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return pp, nil
|
return pp, nil
|
||||||
|
} else if typ == "Balance" {
|
||||||
|
pp, err := pp.NewBalancePaymentProvider()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return pp, nil
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("the payment provider type: %s is not supported", p.Type)
|
return nil, fmt.Errorf("the payment provider type: %s is not supported", p.Type)
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ type Resource struct {
|
|||||||
FileType string `xorm:"varchar(100)" json:"fileType"`
|
FileType string `xorm:"varchar(100)" json:"fileType"`
|
||||||
FileFormat string `xorm:"varchar(100)" json:"fileFormat"`
|
FileFormat string `xorm:"varchar(100)" json:"fileFormat"`
|
||||||
FileSize int `json:"fileSize"`
|
FileSize int `json:"fileSize"`
|
||||||
Url string `xorm:"varchar(255)" json:"url"`
|
Url string `xorm:"varchar(500)" json:"url"`
|
||||||
Description string `xorm:"varchar(255)" json:"description"`
|
Description string `xorm:"varchar(255)" json:"description"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,7 +65,11 @@ func NewSamlResponse(application *Application, user *User, host string, certific
|
|||||||
assertion.CreateAttr("IssueInstant", now)
|
assertion.CreateAttr("IssueInstant", now)
|
||||||
assertion.CreateElement("saml:Issuer").SetText(host)
|
assertion.CreateElement("saml:Issuer").SetText(host)
|
||||||
subject := assertion.CreateElement("saml:Subject")
|
subject := assertion.CreateElement("saml:Subject")
|
||||||
subject.CreateElement("saml:NameID").SetText(user.Name)
|
nameIDValue := user.Name
|
||||||
|
if application.UseEmailAsSamlNameId {
|
||||||
|
nameIDValue = user.Email
|
||||||
|
}
|
||||||
|
subject.CreateElement("saml:NameID").SetText(nameIDValue)
|
||||||
subjectConfirmation := subject.CreateElement("saml:SubjectConfirmation")
|
subjectConfirmation := subject.CreateElement("saml:SubjectConfirmation")
|
||||||
subjectConfirmation.CreateAttr("Method", "urn:oasis:names:tc:SAML:2.0:cm:bearer")
|
subjectConfirmation.CreateAttr("Method", "urn:oasis:names:tc:SAML:2.0:cm:bearer")
|
||||||
subjectConfirmationData := subjectConfirmation.CreateElement("saml:SubjectConfirmationData")
|
subjectConfirmationData := subjectConfirmation.CreateElement("saml:SubjectConfirmationData")
|
||||||
@ -184,17 +188,17 @@ type NameIDFormat struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SingleSignOnService struct {
|
type SingleSignOnService struct {
|
||||||
XMLName xml.Name
|
// XMLName xml.Name
|
||||||
Binding string `xml:"Binding,attr"`
|
Binding string `xml:"Binding,attr"`
|
||||||
Location string `xml:"Location,attr"`
|
Location string `xml:"Location,attr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Attribute struct {
|
type Attribute struct {
|
||||||
// XMLName xml.Name
|
// XMLName xml.Name
|
||||||
|
Xmlns string `xml:"xmlns,attr"`
|
||||||
Name string `xml:"Name,attr"`
|
Name string `xml:"Name,attr"`
|
||||||
NameFormat string `xml:"NameFormat,attr"`
|
NameFormat string `xml:"NameFormat,attr"`
|
||||||
FriendlyName string `xml:"FriendlyName,attr"`
|
FriendlyName string `xml:"FriendlyName,attr"`
|
||||||
Xmlns string `xml:"xmlns,attr"`
|
|
||||||
Values []string `xml:"AttributeValue"`
|
Values []string `xml:"AttributeValue"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -386,7 +390,7 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewSamlResponse11 return a saml1.1 response(not 2.0)
|
// NewSamlResponse11 return a saml1.1 response(not 2.0)
|
||||||
func NewSamlResponse11(user *User, requestID string, host string) (*etree.Element, error) {
|
func NewSamlResponse11(application *Application, user *User, requestID string, host string) (*etree.Element, error) {
|
||||||
samlResponse := &etree.Element{
|
samlResponse := &etree.Element{
|
||||||
Space: "samlp",
|
Space: "samlp",
|
||||||
Tag: "Response",
|
Tag: "Response",
|
||||||
@ -430,7 +434,11 @@ func NewSamlResponse11(user *User, requestID string, host string) (*etree.Elemen
|
|||||||
// nameIdentifier inside subject
|
// nameIdentifier inside subject
|
||||||
nameIdentifier := subject.CreateElement("saml:NameIdentifier")
|
nameIdentifier := subject.CreateElement("saml:NameIdentifier")
|
||||||
// nameIdentifier.CreateAttr("Format", "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress")
|
// nameIdentifier.CreateAttr("Format", "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress")
|
||||||
nameIdentifier.SetText(user.Name)
|
if application.UseEmailAsSamlNameId {
|
||||||
|
nameIdentifier.SetText(user.Email)
|
||||||
|
} else {
|
||||||
|
nameIdentifier.SetText(user.Name)
|
||||||
|
}
|
||||||
|
|
||||||
// subjectConfirmation inside subject
|
// subjectConfirmation inside subject
|
||||||
subjectConfirmation := subject.CreateElement("saml:SubjectConfirmation")
|
subjectConfirmation := subject.CreateElement("saml:SubjectConfirmation")
|
||||||
@ -439,7 +447,11 @@ func NewSamlResponse11(user *User, requestID string, host string) (*etree.Elemen
|
|||||||
attributeStatement := assertion.CreateElement("saml:AttributeStatement")
|
attributeStatement := assertion.CreateElement("saml:AttributeStatement")
|
||||||
subjectInAttribute := attributeStatement.CreateElement("saml:Subject")
|
subjectInAttribute := attributeStatement.CreateElement("saml:Subject")
|
||||||
nameIdentifierInAttribute := subjectInAttribute.CreateElement("saml:NameIdentifier")
|
nameIdentifierInAttribute := subjectInAttribute.CreateElement("saml:NameIdentifier")
|
||||||
nameIdentifierInAttribute.SetText(user.Name)
|
if application.UseEmailAsSamlNameId {
|
||||||
|
nameIdentifierInAttribute.SetText(user.Email)
|
||||||
|
} else {
|
||||||
|
nameIdentifierInAttribute.SetText(user.Name)
|
||||||
|
}
|
||||||
|
|
||||||
subjectConfirmationInAttribute := subjectInAttribute.CreateElement("saml:SubjectConfirmation")
|
subjectConfirmationInAttribute := subjectInAttribute.CreateElement("saml:SubjectConfirmation")
|
||||||
subjectConfirmationInAttribute.CreateElement("saml:ConfirmationMethod").SetText("urn:oasis:names:tc:SAML:1.0:cm:artifact")
|
subjectConfirmationInAttribute.CreateElement("saml:ConfirmationMethod").SetText("urn:oasis:names:tc:SAML:1.0:cm:artifact")
|
||||||
|
@ -30,6 +30,13 @@ import (
|
|||||||
|
|
||||||
var isCloudIntranet bool
|
var isCloudIntranet bool
|
||||||
|
|
||||||
|
const (
|
||||||
|
ProviderTypeGoogleCloudStorage = "Google Cloud Storage"
|
||||||
|
ProviderTypeTencentCloudCOS = "Tencent Cloud COS"
|
||||||
|
ProviderTypeAzureBlob = "Azure Blob"
|
||||||
|
ProviderTypeLocalFileSystem = "Local File System"
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
isCloudIntranet = conf.GetConfigBool("isCloudIntranet")
|
isCloudIntranet = conf.GetConfigBool("isCloudIntranet")
|
||||||
}
|
}
|
||||||
@ -80,27 +87,28 @@ func GetUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool
|
|||||||
objectKey := util.UrlJoin(util.GetUrlPath(provider.Domain), escapedPath)
|
objectKey := util.UrlJoin(util.GetUrlPath(provider.Domain), escapedPath)
|
||||||
|
|
||||||
host := ""
|
host := ""
|
||||||
if provider.Type != "Local File System" {
|
if provider.Type != ProviderTypeLocalFileSystem {
|
||||||
// provider.Domain = "https://cdn.casbin.com/casdoor/"
|
// provider.Domain = "https://cdn.casbin.com/casdoor/"
|
||||||
host = util.GetUrlHost(provider.Domain)
|
host = util.GetUrlHost(provider.Domain)
|
||||||
} else {
|
} else {
|
||||||
// provider.Domain = "http://localhost:8000" or "https://door.casdoor.com"
|
// provider.Domain = "http://localhost:8000" or "https://door.casdoor.com"
|
||||||
host = util.UrlJoin(provider.Domain, "/files")
|
host = util.UrlJoin(provider.Domain, "/files")
|
||||||
}
|
}
|
||||||
if provider.Type == "Azure Blob" {
|
if provider.Type == ProviderTypeAzureBlob || provider.Type == ProviderTypeGoogleCloudStorage {
|
||||||
host = util.UrlJoin(host, provider.Bucket)
|
host = util.UrlJoin(host, provider.Bucket)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileUrl := ""
|
fileUrl := ""
|
||||||
if host != "" {
|
if host != "" {
|
||||||
fileUrl = util.UrlJoin(host, escapePath(objectKey))
|
// fileUrl = util.UrlJoin(host, escapePath(objectKey))
|
||||||
|
fileUrl = util.UrlJoin(host, objectKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
if fileUrl != "" && hasTimestamp {
|
// if fileUrl != "" && hasTimestamp {
|
||||||
fileUrl = fmt.Sprintf("%s?t=%s", fileUrl, util.GetCurrentUnixTime())
|
// fileUrl = fmt.Sprintf("%s?t=%s", fileUrl, util.GetCurrentUnixTime())
|
||||||
}
|
// }
|
||||||
|
|
||||||
if provider.Type == "Tencent Cloud COS" {
|
if provider.Type == ProviderTypeTencentCloudCOS {
|
||||||
objectKey = escapePath(objectKey)
|
objectKey = escapePath(objectKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,7 +117,18 @@ func GetUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool
|
|||||||
|
|
||||||
func getStorageProvider(provider *Provider, lang string) (oss.StorageInterface, error) {
|
func getStorageProvider(provider *Provider, lang string) (oss.StorageInterface, error) {
|
||||||
endpoint := getProviderEndpoint(provider)
|
endpoint := getProviderEndpoint(provider)
|
||||||
storageProvider, err := storage.GetStorageProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.RegionId, provider.Bucket, endpoint)
|
certificate := ""
|
||||||
|
if provider.Category == "Storage" && provider.Type == "Casdoor" {
|
||||||
|
cert, err := GetCert(util.GetId(provider.Owner, provider.Cert))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if cert == nil {
|
||||||
|
return nil, fmt.Errorf("no cert for %s", provider.Cert)
|
||||||
|
}
|
||||||
|
certificate = cert.Certificate
|
||||||
|
}
|
||||||
|
storageProvider, err := storage.GetStorageProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.RegionId, provider.Bucket, endpoint, certificate, provider.Content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -135,17 +154,17 @@ func uploadFile(provider *Provider, fullFilePath string, fileBuffer *bytes.Buffe
|
|||||||
}
|
}
|
||||||
|
|
||||||
fileUrl, objectKey := GetUploadFileUrl(provider, fullFilePath, true)
|
fileUrl, objectKey := GetUploadFileUrl(provider, fullFilePath, true)
|
||||||
|
objectKeyRefined := refineObjectKey(provider, objectKey)
|
||||||
|
|
||||||
objectKeyRefined := objectKey
|
object, err := storageProvider.Put(objectKeyRefined, fileBuffer)
|
||||||
if provider.Type == "Google Cloud Storage" {
|
|
||||||
objectKeyRefined = strings.TrimPrefix(objectKeyRefined, "/")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = storageProvider.Put(objectKeyRefined, fileBuffer)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if provider.Type == "Casdoor" {
|
||||||
|
fileUrl = object.Path
|
||||||
|
}
|
||||||
|
|
||||||
return fileUrl, objectKey, nil
|
return fileUrl, objectKey, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,5 +203,13 @@ func DeleteFile(provider *Provider, objectKey string, lang string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return storageProvider.Delete(objectKey)
|
objectKeyRefined := refineObjectKey(provider, objectKey)
|
||||||
|
return storageProvider.Delete(objectKeyRefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
func refineObjectKey(provider *Provider, objectKey string) string {
|
||||||
|
if provider.Type == ProviderTypeGoogleCloudStorage {
|
||||||
|
return strings.TrimPrefix(objectKey, "/")
|
||||||
|
}
|
||||||
|
return objectKey
|
||||||
}
|
}
|
||||||
|
@ -277,12 +277,11 @@ func GetValidationBySaml(samlRequest string, host string) (string, string, error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if application == nil {
|
if application == nil {
|
||||||
return "", "", fmt.Errorf("the application for user %s is not found", userId)
|
return "", "", fmt.Errorf("the application for user %s is not found", userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
samlResponse, err := NewSamlResponse11(user, request.RequestID, host)
|
samlResponse, err := NewSamlResponse11(application, user, request.RequestID, host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ package object
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
@ -50,48 +51,48 @@ type UserWithoutThirdIdp struct {
|
|||||||
UpdatedTime string `xorm:"varchar(100)" json:"updatedTime"`
|
UpdatedTime string `xorm:"varchar(100)" json:"updatedTime"`
|
||||||
DeletedTime string `xorm:"varchar(100)" json:"deletedTime"`
|
DeletedTime string `xorm:"varchar(100)" json:"deletedTime"`
|
||||||
|
|
||||||
Id string `xorm:"varchar(100) index" json:"id"`
|
Id string `xorm:"varchar(100) index" json:"id"`
|
||||||
Type string `xorm:"varchar(100)" json:"type"`
|
Type string `xorm:"varchar(100)" json:"type"`
|
||||||
Password string `xorm:"varchar(150)" json:"password"`
|
Password string `xorm:"varchar(150)" json:"password"`
|
||||||
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
||||||
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
|
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
|
||||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||||
FirstName string `xorm:"varchar(100)" json:"firstName"`
|
FirstName string `xorm:"varchar(100)" json:"firstName"`
|
||||||
LastName string `xorm:"varchar(100)" json:"lastName"`
|
LastName string `xorm:"varchar(100)" json:"lastName"`
|
||||||
Avatar string `xorm:"varchar(500)" json:"avatar"`
|
Avatar string `xorm:"varchar(500)" json:"avatar"`
|
||||||
AvatarType string `xorm:"varchar(100)" json:"avatarType"`
|
AvatarType string `xorm:"varchar(100)" json:"avatarType"`
|
||||||
PermanentAvatar string `xorm:"varchar(500)" json:"permanentAvatar"`
|
PermanentAvatar string `xorm:"varchar(500)" json:"permanentAvatar"`
|
||||||
Email string `xorm:"varchar(100) index" json:"email"`
|
Email string `xorm:"varchar(100) index" json:"email"`
|
||||||
EmailVerified bool `json:"emailVerified"`
|
EmailVerified bool `json:"emailVerified"`
|
||||||
Phone string `xorm:"varchar(100) index" json:"phone"`
|
Phone string `xorm:"varchar(100) index" json:"phone"`
|
||||||
CountryCode string `xorm:"varchar(6)" json:"countryCode"`
|
CountryCode string `xorm:"varchar(6)" json:"countryCode"`
|
||||||
Region string `xorm:"varchar(100)" json:"region"`
|
Region string `xorm:"varchar(100)" json:"region"`
|
||||||
Location string `xorm:"varchar(100)" json:"location"`
|
Location string `xorm:"varchar(100)" json:"location"`
|
||||||
Address Address `json:"address"`
|
Address []string `json:"address"`
|
||||||
Affiliation string `xorm:"varchar(100)" json:"affiliation"`
|
Affiliation string `xorm:"varchar(100)" json:"affiliation"`
|
||||||
Title string `xorm:"varchar(100)" json:"title"`
|
Title string `xorm:"varchar(100)" json:"title"`
|
||||||
IdCardType string `xorm:"varchar(100)" json:"idCardType"`
|
IdCardType string `xorm:"varchar(100)" json:"idCardType"`
|
||||||
IdCard string `xorm:"varchar(100) index" json:"idCard"`
|
IdCard string `xorm:"varchar(100) index" json:"idCard"`
|
||||||
Homepage string `xorm:"varchar(100)" json:"homepage"`
|
Homepage string `xorm:"varchar(100)" json:"homepage"`
|
||||||
Bio string `xorm:"varchar(100)" json:"bio"`
|
Bio string `xorm:"varchar(100)" json:"bio"`
|
||||||
Tag string `xorm:"varchar(100)" json:"tag"`
|
Tag string `xorm:"varchar(100)" json:"tag"`
|
||||||
Language string `xorm:"varchar(100)" json:"language"`
|
Language string `xorm:"varchar(100)" json:"language"`
|
||||||
Gender string `xorm:"varchar(100)" json:"gender"`
|
Gender string `xorm:"varchar(100)" json:"gender"`
|
||||||
Birthday string `xorm:"varchar(100)" json:"birthday"`
|
Birthday string `xorm:"varchar(100)" json:"birthday"`
|
||||||
Education string `xorm:"varchar(100)" json:"education"`
|
Education string `xorm:"varchar(100)" json:"education"`
|
||||||
Score int `json:"score"`
|
Score int `json:"score"`
|
||||||
Karma int `json:"karma"`
|
Karma int `json:"karma"`
|
||||||
Ranking int `json:"ranking"`
|
Ranking int `json:"ranking"`
|
||||||
IsDefaultAvatar bool `json:"isDefaultAvatar"`
|
IsDefaultAvatar bool `json:"isDefaultAvatar"`
|
||||||
IsOnline bool `json:"isOnline"`
|
IsOnline bool `json:"isOnline"`
|
||||||
IsAdmin bool `json:"isAdmin"`
|
IsAdmin bool `json:"isAdmin"`
|
||||||
IsForbidden bool `json:"isForbidden"`
|
IsForbidden bool `json:"isForbidden"`
|
||||||
IsDeleted bool `json:"isDeleted"`
|
IsDeleted bool `json:"isDeleted"`
|
||||||
SignupApplication string `xorm:"varchar(100)" json:"signupApplication"`
|
SignupApplication string `xorm:"varchar(100)" json:"signupApplication"`
|
||||||
Hash string `xorm:"varchar(100)" json:"hash"`
|
Hash string `xorm:"varchar(100)" json:"hash"`
|
||||||
PreHash string `xorm:"varchar(100)" json:"preHash"`
|
PreHash string `xorm:"varchar(100)" json:"preHash"`
|
||||||
AccessKey string `xorm:"varchar(100)" json:"accessKey"`
|
AccessKey string `xorm:"varchar(100)" json:"accessKey"`
|
||||||
AccessSecret string `xorm:"varchar(100)" json:"accessSecret"`
|
AccessSecret string `xorm:"varchar(100)" json:"accessSecret"`
|
||||||
|
|
||||||
GitHub string `xorm:"github varchar(100)" json:"github"`
|
GitHub string `xorm:"github varchar(100)" json:"github"`
|
||||||
Google string `xorm:"varchar(100)" json:"google"`
|
Google string `xorm:"varchar(100)" json:"google"`
|
||||||
@ -128,7 +129,7 @@ type UserWithoutThirdIdp struct {
|
|||||||
LastSigninWrongTime string `xorm:"varchar(100)" json:"lastSigninWrongTime"`
|
LastSigninWrongTime string `xorm:"varchar(100)" json:"lastSigninWrongTime"`
|
||||||
SigninWrongTimes int `json:"signinWrongTimes"`
|
SigninWrongTimes int `json:"signinWrongTimes"`
|
||||||
|
|
||||||
// ManagedAccounts []ManagedAccount `xorm:"managedAccounts blob" json:"managedAccounts"`
|
ManagedAccounts []ManagedAccount `xorm:"managedAccounts blob" json:"managedAccounts"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClaimsShort struct {
|
type ClaimsShort struct {
|
||||||
@ -139,6 +140,15 @@ type ClaimsShort struct {
|
|||||||
jwt.RegisteredClaims
|
jwt.RegisteredClaims
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OIDCAddress struct {
|
||||||
|
Formatted string `json:"formatted"`
|
||||||
|
StreetAddress string `json:"street_address"`
|
||||||
|
Locality string `json:"locality"`
|
||||||
|
Region string `json:"region"`
|
||||||
|
PostalCode string `json:"postal_code"`
|
||||||
|
Country string `json:"country"`
|
||||||
|
}
|
||||||
|
|
||||||
type ClaimsWithoutThirdIdp struct {
|
type ClaimsWithoutThirdIdp struct {
|
||||||
*UserWithoutThirdIdp
|
*UserWithoutThirdIdp
|
||||||
TokenType string `json:"tokenType,omitempty"`
|
TokenType string `json:"tokenType,omitempty"`
|
||||||
@ -148,15 +158,6 @@ type ClaimsWithoutThirdIdp struct {
|
|||||||
jwt.RegisteredClaims
|
jwt.RegisteredClaims
|
||||||
}
|
}
|
||||||
|
|
||||||
type Address struct {
|
|
||||||
Formatted string `json:"formatted"`
|
|
||||||
StreetAddress string `json:"street_address"`
|
|
||||||
Locality string `json:"locality"`
|
|
||||||
Region string `json:"region"`
|
|
||||||
PostalCode string `json:"postal_code"`
|
|
||||||
Country string `json:"country"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func getShortUser(user *User) *UserShort {
|
func getShortUser(user *User) *UserShort {
|
||||||
res := &UserShort{
|
res := &UserShort{
|
||||||
Owner: user.Owner,
|
Owner: user.Owner,
|
||||||
@ -171,13 +172,6 @@ func getShortUser(user *User) *UserShort {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func getStreetAddress(user *User) string {
|
|
||||||
if len(user.Address) > 0 {
|
|
||||||
return user.Address[0]
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func getUserWithoutThirdIdp(user *User) *UserWithoutThirdIdp {
|
func getUserWithoutThirdIdp(user *User) *UserWithoutThirdIdp {
|
||||||
res := &UserWithoutThirdIdp{
|
res := &UserWithoutThirdIdp{
|
||||||
Owner: user.Owner,
|
Owner: user.Owner,
|
||||||
@ -186,31 +180,24 @@ func getUserWithoutThirdIdp(user *User) *UserWithoutThirdIdp {
|
|||||||
UpdatedTime: user.UpdatedTime,
|
UpdatedTime: user.UpdatedTime,
|
||||||
DeletedTime: user.DeletedTime,
|
DeletedTime: user.DeletedTime,
|
||||||
|
|
||||||
Id: user.Id,
|
Id: user.Id,
|
||||||
Type: user.Type,
|
Type: user.Type,
|
||||||
Password: user.Password,
|
Password: user.Password,
|
||||||
PasswordSalt: user.PasswordSalt,
|
PasswordSalt: user.PasswordSalt,
|
||||||
PasswordType: user.PasswordType,
|
PasswordType: user.PasswordType,
|
||||||
DisplayName: user.DisplayName,
|
DisplayName: user.DisplayName,
|
||||||
FirstName: user.FirstName,
|
FirstName: user.FirstName,
|
||||||
LastName: user.LastName,
|
LastName: user.LastName,
|
||||||
Avatar: user.Avatar,
|
Avatar: user.Avatar,
|
||||||
AvatarType: user.AvatarType,
|
AvatarType: user.AvatarType,
|
||||||
PermanentAvatar: user.PermanentAvatar,
|
PermanentAvatar: user.PermanentAvatar,
|
||||||
Email: user.Email,
|
Email: user.Email,
|
||||||
EmailVerified: user.EmailVerified,
|
EmailVerified: user.EmailVerified,
|
||||||
Phone: user.Phone,
|
Phone: user.Phone,
|
||||||
CountryCode: user.CountryCode,
|
CountryCode: user.CountryCode,
|
||||||
Region: user.Region,
|
Region: user.Region,
|
||||||
Location: user.Location,
|
Location: user.Location,
|
||||||
Address: Address{
|
Address: user.Address,
|
||||||
Formatted: "",
|
|
||||||
StreetAddress: getStreetAddress(user),
|
|
||||||
Locality: "",
|
|
||||||
Region: "",
|
|
||||||
PostalCode: "",
|
|
||||||
Country: "",
|
|
||||||
},
|
|
||||||
Affiliation: user.Affiliation,
|
Affiliation: user.Affiliation,
|
||||||
Title: user.Title,
|
Title: user.Title,
|
||||||
IdCardType: user.IdCardType,
|
IdCardType: user.IdCardType,
|
||||||
@ -268,6 +255,8 @@ func getUserWithoutThirdIdp(user *User) *UserWithoutThirdIdp {
|
|||||||
|
|
||||||
LastSigninWrongTime: user.LastSigninWrongTime,
|
LastSigninWrongTime: user.LastSigninWrongTime,
|
||||||
SigninWrongTimes: user.SigninWrongTimes,
|
SigninWrongTimes: user.SigninWrongTimes,
|
||||||
|
|
||||||
|
ManagedAccounts: user.ManagedAccounts,
|
||||||
}
|
}
|
||||||
|
|
||||||
return res
|
return res
|
||||||
@ -379,6 +368,10 @@ func generateJwtToken(application *Application, user *User, nonce string, scope
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if application.IsShared {
|
||||||
|
claims.Audience = []string{application.ClientId + "-org-" + user.Owner}
|
||||||
|
}
|
||||||
|
|
||||||
var token *jwt.Token
|
var token *jwt.Token
|
||||||
var refreshToken *jwt.Token
|
var refreshToken *jwt.Token
|
||||||
|
|
||||||
@ -386,29 +379,52 @@ func generateJwtToken(application *Application, user *User, nonce string, scope
|
|||||||
application.TokenFormat = "JWT"
|
application.TokenFormat = "JWT"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var jwtMethod jwt.SigningMethod
|
||||||
|
|
||||||
|
if application.TokenSigningMethod == "RS256" {
|
||||||
|
jwtMethod = jwt.SigningMethodRS256
|
||||||
|
} else if application.TokenSigningMethod == "RS512" {
|
||||||
|
jwtMethod = jwt.SigningMethodRS512
|
||||||
|
} else if application.TokenSigningMethod == "ES256" {
|
||||||
|
jwtMethod = jwt.SigningMethodES256
|
||||||
|
} else if application.TokenSigningMethod == "ES512" {
|
||||||
|
jwtMethod = jwt.SigningMethodES512
|
||||||
|
} else if application.TokenSigningMethod == "ES384" {
|
||||||
|
jwtMethod = jwt.SigningMethodES384
|
||||||
|
} else {
|
||||||
|
jwtMethod = jwt.SigningMethodRS256
|
||||||
|
}
|
||||||
|
|
||||||
// the JWT token length in "JWT-Empty" mode will be very short, as User object only has two properties: owner and name
|
// the JWT token length in "JWT-Empty" mode will be very short, as User object only has two properties: owner and name
|
||||||
if application.TokenFormat == "JWT" {
|
if application.TokenFormat == "JWT" {
|
||||||
claimsWithoutThirdIdp := getClaimsWithoutThirdIdp(claims)
|
claimsWithoutThirdIdp := getClaimsWithoutThirdIdp(claims)
|
||||||
|
|
||||||
token = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsWithoutThirdIdp)
|
token = jwt.NewWithClaims(jwtMethod, claimsWithoutThirdIdp)
|
||||||
claimsWithoutThirdIdp.ExpiresAt = jwt.NewNumericDate(refreshExpireTime)
|
claimsWithoutThirdIdp.ExpiresAt = jwt.NewNumericDate(refreshExpireTime)
|
||||||
claimsWithoutThirdIdp.TokenType = "refresh-token"
|
claimsWithoutThirdIdp.TokenType = "refresh-token"
|
||||||
refreshToken = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsWithoutThirdIdp)
|
refreshToken = jwt.NewWithClaims(jwtMethod, claimsWithoutThirdIdp)
|
||||||
} else if application.TokenFormat == "JWT-Empty" {
|
} else if application.TokenFormat == "JWT-Empty" {
|
||||||
claimsShort := getShortClaims(claims)
|
claimsShort := getShortClaims(claims)
|
||||||
|
|
||||||
token = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsShort)
|
token = jwt.NewWithClaims(jwtMethod, claimsShort)
|
||||||
claimsShort.ExpiresAt = jwt.NewNumericDate(refreshExpireTime)
|
claimsShort.ExpiresAt = jwt.NewNumericDate(refreshExpireTime)
|
||||||
claimsShort.TokenType = "refresh-token"
|
claimsShort.TokenType = "refresh-token"
|
||||||
refreshToken = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsShort)
|
refreshToken = jwt.NewWithClaims(jwtMethod, claimsShort)
|
||||||
} else if application.TokenFormat == "JWT-Custom" {
|
} else if application.TokenFormat == "JWT-Custom" {
|
||||||
claimsCustom := getClaimsCustom(claims, application.TokenFields)
|
claimsCustom := getClaimsCustom(claims, application.TokenFields)
|
||||||
|
|
||||||
token = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsCustom)
|
token = jwt.NewWithClaims(jwtMethod, claimsCustom)
|
||||||
refreshClaims := getClaimsCustom(claims, application.TokenFields)
|
refreshClaims := getClaimsCustom(claims, application.TokenFields)
|
||||||
refreshClaims["exp"] = jwt.NewNumericDate(refreshExpireTime)
|
refreshClaims["exp"] = jwt.NewNumericDate(refreshExpireTime)
|
||||||
refreshClaims["TokenType"] = "refresh-token"
|
refreshClaims["TokenType"] = "refresh-token"
|
||||||
refreshToken = jwt.NewWithClaims(jwt.SigningMethodRS256, refreshClaims)
|
refreshToken = jwt.NewWithClaims(jwtMethod, refreshClaims)
|
||||||
|
} else if application.TokenFormat == "JWT-Standard" {
|
||||||
|
claimsStandard := getStandardClaims(claims)
|
||||||
|
|
||||||
|
token = jwt.NewWithClaims(jwtMethod, claimsStandard)
|
||||||
|
claimsStandard.ExpiresAt = jwt.NewNumericDate(refreshExpireTime)
|
||||||
|
claimsStandard.TokenType = "refresh-token"
|
||||||
|
refreshToken = jwt.NewWithClaims(jwtMethod, claimsStandard)
|
||||||
} else {
|
} else {
|
||||||
return "", "", "", fmt.Errorf("unknown application TokenFormat: %s", application.TokenFormat)
|
return "", "", "", fmt.Errorf("unknown application TokenFormat: %s", application.TokenFormat)
|
||||||
}
|
}
|
||||||
@ -426,34 +442,57 @@ func generateJwtToken(application *Application, user *User, nonce string, scope
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RSA private key
|
var (
|
||||||
key, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(cert.PrivateKey))
|
tokenString string
|
||||||
|
refreshTokenString string
|
||||||
|
key interface{}
|
||||||
|
)
|
||||||
|
|
||||||
|
if strings.Contains(application.TokenSigningMethod, "RS") || application.TokenSigningMethod == "" {
|
||||||
|
// RSA private key
|
||||||
|
key, err = jwt.ParseRSAPrivateKeyFromPEM([]byte(cert.PrivateKey))
|
||||||
|
} else if strings.Contains(application.TokenSigningMethod, "ES") {
|
||||||
|
// ES private key
|
||||||
|
key, err = jwt.ParseECPrivateKeyFromPEM([]byte(cert.PrivateKey))
|
||||||
|
} else if strings.Contains(application.TokenSigningMethod, "Ed") {
|
||||||
|
// Ed private key
|
||||||
|
key, err = jwt.ParseEdPrivateKeyFromPEM([]byte(cert.PrivateKey))
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", err
|
return "", "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
token.Header["kid"] = cert.Name
|
token.Header["kid"] = cert.Name
|
||||||
tokenString, err := token.SignedString(key)
|
tokenString, err = token.SignedString(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", err
|
return "", "", "", err
|
||||||
}
|
}
|
||||||
refreshTokenString, err := refreshToken.SignedString(key)
|
refreshTokenString, err = refreshToken.SignedString(key)
|
||||||
|
|
||||||
return tokenString, refreshTokenString, name, err
|
return tokenString, refreshTokenString, name, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseJwtToken(token string, cert *Cert) (*Claims, error) {
|
func ParseJwtToken(token string, cert *Cert) (*Claims, error) {
|
||||||
t, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
|
t, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
|
||||||
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
|
var (
|
||||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
certificate interface{}
|
||||||
}
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
if cert.Certificate == "" {
|
if cert.Certificate == "" {
|
||||||
return nil, fmt.Errorf("the certificate field should not be empty for the cert: %v", cert)
|
return nil, fmt.Errorf("the certificate field should not be empty for the cert: %v", cert)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RSA certificate
|
if _, ok := token.Method.(*jwt.SigningMethodRSA); ok {
|
||||||
certificate, err := jwt.ParseRSAPublicKeyFromPEM([]byte(cert.Certificate))
|
// RSA certificate
|
||||||
|
certificate, err = jwt.ParseRSAPublicKeyFromPEM([]byte(cert.Certificate))
|
||||||
|
} else if _, ok := token.Method.(*jwt.SigningMethodECDSA); ok {
|
||||||
|
// ES certificate
|
||||||
|
certificate, err = jwt.ParseECPublicKeyFromPEM([]byte(cert.Certificate))
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -309,12 +309,22 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = ParseJwtToken(refreshToken, cert)
|
if application.TokenFormat == "JWT-Standard" {
|
||||||
if err != nil {
|
_, err = ParseStandardJwtToken(refreshToken, cert)
|
||||||
return &TokenError{
|
if err != nil {
|
||||||
Error: InvalidGrant,
|
return &TokenError{
|
||||||
ErrorDescription: fmt.Sprintf("parse refresh token error: %s", err.Error()),
|
Error: InvalidGrant,
|
||||||
}, nil
|
ErrorDescription: fmt.Sprintf("parse refresh token error: %s", err.Error()),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_, err = ParseJwtToken(refreshToken, cert)
|
||||||
|
if err != nil {
|
||||||
|
return &TokenError{
|
||||||
|
Error: InvalidGrant,
|
||||||
|
ErrorDescription: fmt.Sprintf("parse refresh token error: %s", err.Error()),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate a new token
|
// generate a new token
|
||||||
@ -418,22 +428,26 @@ func GetAuthorizationCodeToken(application *Application, clientSecret string, co
|
|||||||
if token == nil {
|
if token == nil {
|
||||||
return nil, &TokenError{
|
return nil, &TokenError{
|
||||||
Error: InvalidGrant,
|
Error: InvalidGrant,
|
||||||
ErrorDescription: "authorization code is invalid",
|
ErrorDescription: fmt.Sprintf("authorization code: [%s] is invalid", code),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if token.CodeIsUsed {
|
if token.CodeIsUsed {
|
||||||
// anti replay attacks
|
// anti replay attacks
|
||||||
return nil, &TokenError{
|
return nil, &TokenError{
|
||||||
Error: InvalidGrant,
|
Error: InvalidGrant,
|
||||||
ErrorDescription: "authorization code has been used",
|
ErrorDescription: fmt.Sprintf("authorization code has been used for token: [%s]", token.GetId()),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if token.CodeChallenge != "" && pkceChallenge(verifier) != token.CodeChallenge {
|
if token.CodeChallenge != "" {
|
||||||
return nil, &TokenError{
|
challengeAnswer := pkceChallenge(verifier)
|
||||||
Error: InvalidGrant,
|
if challengeAnswer != token.CodeChallenge {
|
||||||
ErrorDescription: "verifier is invalid",
|
return nil, &TokenError{
|
||||||
}, nil
|
Error: InvalidGrant,
|
||||||
|
ErrorDescription: fmt.Sprintf("verifier is invalid, challengeAnswer: [%s], token.CodeChallenge: [%s]", challengeAnswer, token.CodeChallenge),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if application.ClientSecret != clientSecret {
|
if application.ClientSecret != clientSecret {
|
||||||
@ -442,13 +456,13 @@ func GetAuthorizationCodeToken(application *Application, clientSecret string, co
|
|||||||
if token.CodeChallenge == "" {
|
if token.CodeChallenge == "" {
|
||||||
return nil, &TokenError{
|
return nil, &TokenError{
|
||||||
Error: InvalidClient,
|
Error: InvalidClient,
|
||||||
ErrorDescription: "client_secret is invalid",
|
ErrorDescription: fmt.Sprintf("client_secret is invalid for application: [%s], token.CodeChallenge: empty", application.GetId()),
|
||||||
}, nil
|
}, nil
|
||||||
} else {
|
} else {
|
||||||
if clientSecret != "" {
|
if clientSecret != "" {
|
||||||
return nil, &TokenError{
|
return nil, &TokenError{
|
||||||
Error: InvalidClient,
|
Error: InvalidClient,
|
||||||
ErrorDescription: "client_secret is invalid",
|
ErrorDescription: fmt.Sprintf("client_secret is invalid for application: [%s], token.CodeChallenge: [%s]", application.GetId(), token.CodeChallenge),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -457,15 +471,16 @@ func GetAuthorizationCodeToken(application *Application, clientSecret string, co
|
|||||||
if application.Name != token.Application {
|
if application.Name != token.Application {
|
||||||
return nil, &TokenError{
|
return nil, &TokenError{
|
||||||
Error: InvalidGrant,
|
Error: InvalidGrant,
|
||||||
ErrorDescription: "the token is for wrong application (client_id)",
|
ErrorDescription: fmt.Sprintf("the token is for wrong application (client_id), application.Name: [%s], token.Application: [%s]", application.Name, token.Application),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if time.Now().Unix() > token.CodeExpireIn {
|
nowUnix := time.Now().Unix()
|
||||||
|
if nowUnix > token.CodeExpireIn {
|
||||||
// code must be used within 5 minutes
|
// code must be used within 5 minutes
|
||||||
return nil, &TokenError{
|
return nil, &TokenError{
|
||||||
Error: InvalidGrant,
|
Error: InvalidGrant,
|
||||||
ErrorDescription: "authorization code has expired",
|
ErrorDescription: fmt.Sprintf("authorization code has expired, nowUnix: [%s], token.CodeExpireIn: [%s]", time.Unix(nowUnix, 0).Format(time.RFC3339), time.Unix(token.CodeExpireIn, 0).Format(time.RFC3339)),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
return token, nil, nil
|
return token, nil, nil
|
||||||
|
121
object/token_standard_jwt.go
Normal file
121
object/token_standard_jwt.go
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
// 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"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
"github.com/golang-jwt/jwt/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ClaimsStandard struct {
|
||||||
|
*UserShort
|
||||||
|
EmailVerified bool `json:"email_verified,omitempty"`
|
||||||
|
PhoneNumber string `json:"phone_number,omitempty"`
|
||||||
|
PhoneNumberVerified bool `json:"phone_number_verified,omitempty"`
|
||||||
|
Gender string `json:"gender,omitempty"`
|
||||||
|
TokenType string `json:"tokenType,omitempty"`
|
||||||
|
Nonce string `json:"nonce,omitempty"`
|
||||||
|
Scope string `json:"scope,omitempty"`
|
||||||
|
Address OIDCAddress `json:"address,omitempty"`
|
||||||
|
|
||||||
|
jwt.RegisteredClaims
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStreetAddress(user *User) string {
|
||||||
|
var addrs string
|
||||||
|
for _, addr := range user.Address {
|
||||||
|
addrs += addr + "\n"
|
||||||
|
}
|
||||||
|
return addrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStandardClaims(claims Claims) ClaimsStandard {
|
||||||
|
res := ClaimsStandard{
|
||||||
|
UserShort: getShortUser(claims.User),
|
||||||
|
EmailVerified: claims.User.EmailVerified,
|
||||||
|
TokenType: claims.TokenType,
|
||||||
|
Nonce: claims.Nonce,
|
||||||
|
Scope: claims.Scope,
|
||||||
|
RegisteredClaims: claims.RegisteredClaims,
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Phone = ""
|
||||||
|
var scopes []string
|
||||||
|
|
||||||
|
if strings.Contains(claims.Scope, ",") {
|
||||||
|
scopes = strings.Split(claims.Scope, ",")
|
||||||
|
} else {
|
||||||
|
scopes = strings.Split(claims.Scope, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, scope := range scopes {
|
||||||
|
if scope == "address" {
|
||||||
|
res.Address = OIDCAddress{StreetAddress: getStreetAddress(claims.User)}
|
||||||
|
} else if scope == "profile" {
|
||||||
|
res.Gender = claims.User.Gender
|
||||||
|
} else if scope == "phone" && claims.User.Phone != "" {
|
||||||
|
res.PhoneNumberVerified = true
|
||||||
|
phoneNumber, ok := util.GetE164Number(claims.User.Phone, claims.User.CountryCode)
|
||||||
|
if !ok {
|
||||||
|
res.PhoneNumberVerified = false
|
||||||
|
} else {
|
||||||
|
res.PhoneNumber = phoneNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseStandardJwtToken(token string, cert *Cert) (*ClaimsStandard, error) {
|
||||||
|
t, err := jwt.ParseWithClaims(token, &ClaimsStandard{}, func(token *jwt.Token) (interface{}, error) {
|
||||||
|
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
|
||||||
|
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if cert.Certificate == "" {
|
||||||
|
return nil, fmt.Errorf("the certificate field should not be empty for the cert: %v", cert)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RSA certificate
|
||||||
|
certificate, err := jwt.ParseRSAPublicKeyFromPEM([]byte(cert.Certificate))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return certificate, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if t != nil {
|
||||||
|
if claims, ok := t.Claims.(*ClaimsStandard); ok && t.Valid {
|
||||||
|
return claims, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseStandardJwtTokenByApplication(token string, application *Application) (*ClaimsStandard, error) {
|
||||||
|
cert, err := getCertByApplication(application)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParseStandardJwtToken(token, cert)
|
||||||
|
}
|
@ -17,6 +17,7 @@ package object
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/pp"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"github.com/xorm-io/core"
|
"github.com/xorm-io/core"
|
||||||
)
|
)
|
||||||
@ -43,7 +44,7 @@ type Transaction struct {
|
|||||||
Application string `xorm:"varchar(100)" json:"application"`
|
Application string `xorm:"varchar(100)" json:"application"`
|
||||||
Payment string `xorm:"varchar(100)" json:"payment"`
|
Payment string `xorm:"varchar(100)" json:"payment"`
|
||||||
|
|
||||||
State string `xorm:"varchar(100)" json:"state"`
|
State pp.PaymentState `xorm:"varchar(100)" json:"state"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetTransactionCount(owner, field, value string) (int64, error) {
|
func GetTransactionCount(owner, field, value string) (int64, error) {
|
||||||
|
@ -204,6 +204,7 @@ type User struct {
|
|||||||
SigninWrongTimes int `json:"signinWrongTimes"`
|
SigninWrongTimes int `json:"signinWrongTimes"`
|
||||||
|
|
||||||
ManagedAccounts []ManagedAccount `xorm:"managedAccounts blob" json:"managedAccounts"`
|
ManagedAccounts []ManagedAccount `xorm:"managedAccounts blob" json:"managedAccounts"`
|
||||||
|
MfaAccounts []MfaAccount `xorm:"mfaAccounts blob" json:"mfaAccounts"`
|
||||||
NeedUpdatePassword bool `json:"needUpdatePassword"`
|
NeedUpdatePassword bool `json:"needUpdatePassword"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,6 +231,12 @@ type ManagedAccount struct {
|
|||||||
SigninUrl string `xorm:"varchar(200)" json:"signinUrl"`
|
SigninUrl string `xorm:"varchar(200)" json:"signinUrl"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MfaAccount struct {
|
||||||
|
AccountName string `xorm:"varchar(100)" json:"accountName"`
|
||||||
|
Issuer string `xorm:"varchar(100)" json:"issuer"`
|
||||||
|
SecretKey string `xorm:"varchar(100)" json:"secretKey"`
|
||||||
|
}
|
||||||
|
|
||||||
type FaceId struct {
|
type FaceId struct {
|
||||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||||
FaceIdData []float64 `json:"faceIdData"`
|
FaceIdData []float64 `json:"faceIdData"`
|
||||||
@ -603,6 +610,12 @@ func GetMaskedUser(user *User, isAdminOrSelf bool, errs ...error) (*User, error)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if user.MfaAccounts != nil {
|
||||||
|
for _, mfaAccount := range user.MfaAccounts {
|
||||||
|
mfaAccount.SecretKey = "***"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if user.TotpSecret != "" {
|
if user.TotpSecret != "" {
|
||||||
user.TotpSecret = ""
|
user.TotpSecret = ""
|
||||||
}
|
}
|
||||||
@ -675,7 +688,7 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
|
|||||||
columns = []string{
|
columns = []string{
|
||||||
"owner", "display_name", "avatar", "first_name", "last_name",
|
"owner", "display_name", "avatar", "first_name", "last_name",
|
||||||
"location", "address", "country_code", "region", "language", "affiliation", "title", "id_card_type", "id_card", "homepage", "bio", "tag", "language", "gender", "birthday", "education", "score", "karma", "ranking", "signup_application",
|
"location", "address", "country_code", "region", "language", "affiliation", "title", "id_card_type", "id_card", "homepage", "bio", "tag", "language", "gender", "birthday", "education", "score", "karma", "ranking", "signup_application",
|
||||||
"is_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts", "face_ids",
|
"is_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts", "face_ids", "mfaAccounts",
|
||||||
"signin_wrong_times", "last_signin_wrong_time", "groups", "access_key", "access_secret", "mfa_phone_enabled", "mfa_email_enabled",
|
"signin_wrong_times", "last_signin_wrong_time", "groups", "access_key", "access_secret", "mfa_phone_enabled", "mfa_email_enabled",
|
||||||
"github", "google", "qq", "wechat", "facebook", "dingtalk", "weibo", "gitee", "linkedin", "wecom", "lark", "gitlab", "adfs",
|
"github", "google", "qq", "wechat", "facebook", "dingtalk", "weibo", "gitee", "linkedin", "wecom", "lark", "gitlab", "adfs",
|
||||||
"baidu", "alipay", "casdoor", "infoflow", "apple", "azuread", "azureadb2c", "slack", "steam", "bilibili", "okta", "douyin", "line", "amazon",
|
"baidu", "alipay", "casdoor", "infoflow", "apple", "azuread", "azureadb2c", "slack", "steam", "bilibili", "okta", "douyin", "line", "amazon",
|
||||||
@ -937,7 +950,17 @@ func DeleteUser(user *User) (bool, error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return deleteUser(user)
|
organization, err := GetOrganizationByUser(user)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if organization != nil && organization.EnableSoftDeletion {
|
||||||
|
user.IsDeleted = true
|
||||||
|
user.DeletedTime = util.GetCurrentTime()
|
||||||
|
return UpdateUser(user.GetId(), user, []string{"is_deleted", "deleted_time"}, false)
|
||||||
|
} else {
|
||||||
|
return deleteUser(user)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetUserInfo(user *User, scope string, aud string, host string) (*Userinfo, error) {
|
func GetUserInfo(user *User, scope string, aud string, host string) (*Userinfo, error) {
|
||||||
@ -1125,7 +1148,7 @@ func (user *User) IsApplicationAdmin(application *Application) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return (user.Owner == application.Organization && user.IsAdmin) || user.IsGlobalAdmin()
|
return (user.Owner == application.Organization && user.IsAdmin) || user.IsGlobalAdmin() || (user.IsAdmin && application.IsShared)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (user *User) IsGlobalAdmin() bool {
|
func (user *User) IsGlobalAdmin() bool {
|
||||||
@ -1158,7 +1181,7 @@ func GenerateIdForNewUser(application *Application) (string, error) {
|
|||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUserBalance(owner string, name string, balance float64) error {
|
func UpdateUserBalance(owner string, name string, balance float64) error {
|
||||||
user, err := getUser(owner, name)
|
user, err := getUser(owner, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -271,113 +271,213 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
|
|
||||||
if oldUser.Owner != newUser.Owner {
|
if oldUser.Owner != newUser.Owner {
|
||||||
item := GetAccountItemByName("Organization", organization)
|
item := GetAccountItemByName("Organization", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Owner = oldUser.Owner
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Name != newUser.Name {
|
if oldUser.Name != newUser.Name {
|
||||||
item := GetAccountItemByName("Name", organization)
|
item := GetAccountItemByName("Name", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Name = oldUser.Name
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Id != newUser.Id {
|
if oldUser.Id != newUser.Id {
|
||||||
item := GetAccountItemByName("ID", organization)
|
item := GetAccountItemByName("ID", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Id = oldUser.Id
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.DisplayName != newUser.DisplayName {
|
if oldUser.DisplayName != newUser.DisplayName {
|
||||||
item := GetAccountItemByName("Display name", organization)
|
item := GetAccountItemByName("Display name", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.DisplayName = oldUser.DisplayName
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Avatar != newUser.Avatar {
|
if oldUser.Avatar != newUser.Avatar {
|
||||||
item := GetAccountItemByName("Avatar", organization)
|
item := GetAccountItemByName("Avatar", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Avatar = oldUser.Avatar
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Type != newUser.Type {
|
if oldUser.Type != newUser.Type {
|
||||||
item := GetAccountItemByName("User type", organization)
|
item := GetAccountItemByName("User type", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Type = oldUser.Type
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// The password is *** when not modified
|
// The password is *** when not modified
|
||||||
if oldUser.Password != newUser.Password && newUser.Password != "***" {
|
if oldUser.Password != newUser.Password && newUser.Password != "***" {
|
||||||
item := GetAccountItemByName("Password", organization)
|
item := GetAccountItemByName("Password", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Password = oldUser.Password
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Email != newUser.Email {
|
if oldUser.Email != newUser.Email {
|
||||||
item := GetAccountItemByName("Email", organization)
|
item := GetAccountItemByName("Email", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Email = oldUser.Email
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Phone != newUser.Phone {
|
if oldUser.Phone != newUser.Phone {
|
||||||
item := GetAccountItemByName("Phone", organization)
|
item := GetAccountItemByName("Phone", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Phone = oldUser.Phone
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.CountryCode != newUser.CountryCode {
|
if oldUser.CountryCode != newUser.CountryCode {
|
||||||
item := GetAccountItemByName("Country code", organization)
|
item := GetAccountItemByName("Country code", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.CountryCode = oldUser.CountryCode
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Region != newUser.Region {
|
if oldUser.Region != newUser.Region {
|
||||||
item := GetAccountItemByName("Country/Region", organization)
|
item := GetAccountItemByName("Country/Region", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Region = oldUser.Region
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Location != newUser.Location {
|
if oldUser.Location != newUser.Location {
|
||||||
item := GetAccountItemByName("Location", organization)
|
item := GetAccountItemByName("Location", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Location = oldUser.Location
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Affiliation != newUser.Affiliation {
|
if oldUser.Affiliation != newUser.Affiliation {
|
||||||
item := GetAccountItemByName("Affiliation", organization)
|
item := GetAccountItemByName("Affiliation", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Affiliation = oldUser.Affiliation
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Title != newUser.Title {
|
if oldUser.Title != newUser.Title {
|
||||||
item := GetAccountItemByName("Title", organization)
|
item := GetAccountItemByName("Title", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Title = oldUser.Title
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Homepage != newUser.Homepage {
|
if oldUser.Homepage != newUser.Homepage {
|
||||||
item := GetAccountItemByName("Homepage", organization)
|
item := GetAccountItemByName("Homepage", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Homepage = oldUser.Homepage
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Bio != newUser.Bio {
|
if oldUser.Bio != newUser.Bio {
|
||||||
item := GetAccountItemByName("Bio", organization)
|
item := GetAccountItemByName("Bio", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Bio = oldUser.Bio
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Tag != newUser.Tag {
|
if oldUser.Tag != newUser.Tag {
|
||||||
item := GetAccountItemByName("Tag", organization)
|
item := GetAccountItemByName("Tag", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Tag = oldUser.Tag
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.SignupApplication != newUser.SignupApplication {
|
if oldUser.SignupApplication != newUser.SignupApplication {
|
||||||
item := GetAccountItemByName("Signup application", organization)
|
item := GetAccountItemByName("Signup application", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.SignupApplication = oldUser.SignupApplication
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Gender != newUser.Gender {
|
if oldUser.Gender != newUser.Gender {
|
||||||
item := GetAccountItemByName("Gender", organization)
|
item := GetAccountItemByName("Gender", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Gender = oldUser.Gender
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Birthday != newUser.Birthday {
|
if oldUser.Birthday != newUser.Birthday {
|
||||||
item := GetAccountItemByName("Birthday", organization)
|
item := GetAccountItemByName("Birthday", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Birthday = oldUser.Birthday
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Education != newUser.Education {
|
if oldUser.Education != newUser.Education {
|
||||||
item := GetAccountItemByName("Education", organization)
|
item := GetAccountItemByName("Education", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Education = oldUser.Education
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.IdCard != newUser.IdCard {
|
if oldUser.IdCard != newUser.IdCard {
|
||||||
item := GetAccountItemByName("ID card", organization)
|
item := GetAccountItemByName("ID card", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.IdCard = oldUser.IdCard
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.IdCardType != newUser.IdCardType {
|
if oldUser.IdCardType != newUser.IdCardType {
|
||||||
item := GetAccountItemByName("ID card type", organization)
|
item := GetAccountItemByName("ID card type", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.IdCardType = oldUser.IdCardType
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
oldUserPropertiesJson, _ := json.Marshal(oldUser.Properties)
|
oldUserPropertiesJson, _ := json.Marshal(oldUser.Properties)
|
||||||
newUserPropertiesJson, _ := json.Marshal(newUser.Properties)
|
newUserPropertiesJson, _ := json.Marshal(newUser.Properties)
|
||||||
if string(oldUserPropertiesJson) != string(newUserPropertiesJson) {
|
if string(oldUserPropertiesJson) != string(newUserPropertiesJson) {
|
||||||
item := GetAccountItemByName("Properties", organization)
|
item := GetAccountItemByName("Properties", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Properties = oldUser.Properties
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.PreferredMfaType != newUser.PreferredMfaType {
|
if oldUser.PreferredMfaType != newUser.PreferredMfaType {
|
||||||
item := GetAccountItemByName("Multi-factor authentication", organization)
|
item := GetAccountItemByName("Multi-factor authentication", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.PreferredMfaType = oldUser.PreferredMfaType
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Groups == nil {
|
if oldUser.Groups == nil {
|
||||||
@ -390,40 +490,135 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
newUserGroupsJson, _ := json.Marshal(newUser.Groups)
|
newUserGroupsJson, _ := json.Marshal(newUser.Groups)
|
||||||
if string(oldUserGroupsJson) != string(newUserGroupsJson) {
|
if string(oldUserGroupsJson) != string(newUserGroupsJson) {
|
||||||
item := GetAccountItemByName("Groups", organization)
|
item := GetAccountItemByName("Groups", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Groups = oldUser.Groups
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if oldUser.Address == nil {
|
||||||
|
oldUser.Address = []string{}
|
||||||
|
}
|
||||||
|
oldUserAddressJson, _ := json.Marshal(oldUser.Address)
|
||||||
|
|
||||||
|
if newUser.Address == nil {
|
||||||
|
newUser.Address = []string{}
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if newUser.FaceIds != nil {
|
if newUser.FaceIds != nil {
|
||||||
item := GetAccountItemByName("Face ID", organization)
|
item := GetAccountItemByName("Face ID", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.FaceIds = oldUser.FaceIds
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.IsAdmin != newUser.IsAdmin {
|
if oldUser.IsAdmin != newUser.IsAdmin {
|
||||||
item := GetAccountItemByName("Is admin", organization)
|
item := GetAccountItemByName("Is admin", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.IsAdmin = oldUser.IsAdmin
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.IsForbidden != newUser.IsForbidden {
|
if oldUser.IsForbidden != newUser.IsForbidden {
|
||||||
item := GetAccountItemByName("Is forbidden", organization)
|
item := GetAccountItemByName("Is forbidden", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.IsForbidden = oldUser.IsForbidden
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.IsDeleted != newUser.IsDeleted {
|
if oldUser.IsDeleted != newUser.IsDeleted {
|
||||||
item := GetAccountItemByName("Is deleted", organization)
|
item := GetAccountItemByName("Is deleted", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.IsDeleted = oldUser.IsDeleted
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.NeedUpdatePassword != newUser.NeedUpdatePassword {
|
if oldUser.NeedUpdatePassword != newUser.NeedUpdatePassword {
|
||||||
item := GetAccountItemByName("Need update password", organization)
|
item := GetAccountItemByName("Need update password", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.NeedUpdatePassword = oldUser.NeedUpdatePassword
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Balance != newUser.Balance {
|
if oldUser.Balance != newUser.Balance {
|
||||||
item := GetAccountItemByName("Balance", organization)
|
item := GetAccountItemByName("Balance", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Balance = oldUser.Balance
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Score != newUser.Score {
|
if oldUser.Score != newUser.Score {
|
||||||
item := GetAccountItemByName("Score", organization)
|
item := GetAccountItemByName("Score", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Score = oldUser.Score
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if oldUser.Karma != newUser.Karma {
|
||||||
|
item := GetAccountItemByName("Karma", organization)
|
||||||
|
if item == nil {
|
||||||
|
newUser.Karma = oldUser.Karma
|
||||||
|
} else {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if oldUser.Ranking != newUser.Ranking {
|
||||||
|
item := GetAccountItemByName("Ranking", organization)
|
||||||
|
if item == nil {
|
||||||
|
newUser.Ranking = oldUser.Ranking
|
||||||
|
} else {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if oldUser.Hash != newUser.Hash {
|
||||||
|
item := GetAccountItemByName("Hash", organization)
|
||||||
|
if item == nil {
|
||||||
|
newUser.Hash = oldUser.Hash
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, accountItem := range itemsChanged {
|
for _, accountItem := range itemsChanged {
|
||||||
|
@ -166,19 +166,76 @@ func AddToVerificationRecord(user *User, provider *Provider, remoteAddr, recordT
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func filterRecordIn24Hours(record *VerificationRecord) *VerificationRecord {
|
||||||
|
if record == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
|
if now-record.Time > 60*60*24 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return record
|
||||||
|
}
|
||||||
|
|
||||||
func getVerificationRecord(dest string) (*VerificationRecord, error) {
|
func getVerificationRecord(dest string) (*VerificationRecord, error) {
|
||||||
var record VerificationRecord
|
record := &VerificationRecord{}
|
||||||
record.Receiver = dest
|
record.Receiver = dest
|
||||||
|
|
||||||
has, err := ormer.Engine.Desc("time").Where("is_used = false").Get(&record)
|
has, err := ormer.Engine.Desc("time").Where("is_used = false").Get(record)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
record = filterRecordIn24Hours(record)
|
||||||
|
if record == nil {
|
||||||
|
has = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !has {
|
||||||
|
record = &VerificationRecord{}
|
||||||
|
record.Receiver = dest
|
||||||
|
|
||||||
|
has, err = ormer.Engine.Desc("time").Get(record)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
record = filterRecordIn24Hours(record)
|
||||||
|
if record == nil {
|
||||||
|
has = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !has {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return record, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return record, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUnusedVerificationRecord(dest string) (*VerificationRecord, error) {
|
||||||
|
record := &VerificationRecord{}
|
||||||
|
record.Receiver = dest
|
||||||
|
|
||||||
|
has, err := ormer.Engine.Desc("time").Where("is_used = false").Get(record)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
record = filterRecordIn24Hours(record)
|
||||||
|
if record == nil {
|
||||||
|
has = false
|
||||||
|
}
|
||||||
|
|
||||||
if !has {
|
if !has {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return &record, nil
|
return record, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckVerificationCode(dest string, code string, lang string) (*VerifyResult, error) {
|
func CheckVerificationCode(dest string, code string, lang string) (*VerifyResult, error) {
|
||||||
@ -187,7 +244,9 @@ func CheckVerificationCode(dest string, code string, lang string) (*VerifyResult
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if record == nil {
|
if record == nil {
|
||||||
return &VerifyResult{noRecordError, i18n.Translate(lang, "verification:The verification code has not been sent yet, or has already been used!")}, nil
|
return &VerifyResult{noRecordError, i18n.Translate(lang, "verification:The verification code has not been sent yet!")}, nil
|
||||||
|
} else if record.IsUsed {
|
||||||
|
return &VerifyResult{noRecordError, i18n.Translate(lang, "verification:The verification code has already been used!")}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
timeoutInMinutes, err := conf.GetConfigInt64("verificationCodeTimeout")
|
timeoutInMinutes, err := conf.GetConfigInt64("verificationCodeTimeout")
|
||||||
@ -196,9 +255,6 @@ func CheckVerificationCode(dest string, code string, lang string) (*VerifyResult
|
|||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
if now-record.Time > timeoutInMinutes*60*10 {
|
|
||||||
return &VerifyResult{noRecordError, i18n.Translate(lang, "verification:The verification code has not been sent yet!")}, nil
|
|
||||||
}
|
|
||||||
if now-record.Time > timeoutInMinutes*60 {
|
if now-record.Time > timeoutInMinutes*60 {
|
||||||
return &VerifyResult{timeoutError, fmt.Sprintf(i18n.Translate(lang, "verification:You should verify your code in %d min!"), timeoutInMinutes)}, nil
|
return &VerifyResult{timeoutError, fmt.Sprintf(i18n.Translate(lang, "verification:You should verify your code in %d min!"), timeoutInMinutes)}, nil
|
||||||
}
|
}
|
||||||
@ -211,7 +267,7 @@ func CheckVerificationCode(dest string, code string, lang string) (*VerifyResult
|
|||||||
}
|
}
|
||||||
|
|
||||||
func DisableVerificationCode(dest string) error {
|
func DisableVerificationCode(dest string) error {
|
||||||
record, err := getVerificationRecord(dest)
|
record, err := getUnusedVerificationRecord(dest)
|
||||||
if record == nil || err != nil {
|
if record == nil || err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
50
pp/balance.go
Normal file
50
pp/balance.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
// 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 pp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BalancePaymentProvider struct{}
|
||||||
|
|
||||||
|
func NewBalancePaymentProvider() (*BalancePaymentProvider, error) {
|
||||||
|
pp := &BalancePaymentProvider{}
|
||||||
|
return pp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pp *BalancePaymentProvider) Pay(r *PayReq) (*PayResp, error) {
|
||||||
|
owner, _ := util.GetOwnerAndNameFromId(r.PayerId)
|
||||||
|
return &PayResp{
|
||||||
|
PayUrl: r.ReturnUrl,
|
||||||
|
OrderId: fmt.Sprintf("%s/%s", owner, r.PaymentName),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pp *BalancePaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {
|
||||||
|
return &NotifyResult{
|
||||||
|
PaymentStatus: PaymentStatePaid,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pp *BalancePaymentProvider) GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error) {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pp *BalancePaymentProvider) GetResponseError(err error) string {
|
||||||
|
return ""
|
||||||
|
}
|
@ -18,6 +18,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/conf"
|
"github.com/casdoor/casdoor/conf"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
@ -27,6 +28,14 @@ import (
|
|||||||
"layeh.com/radius/rfc2866"
|
"layeh.com/radius/rfc2866"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var StateMap map[string]AccessStateContent
|
||||||
|
|
||||||
|
const StateExpiredTime = time.Second * 120
|
||||||
|
|
||||||
|
type AccessStateContent struct {
|
||||||
|
ExpiredAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
func StartRadiusServer() {
|
func StartRadiusServer() {
|
||||||
secret := conf.GetConfigString("radiusSecret")
|
secret := conf.GetConfigString("radiusSecret")
|
||||||
server := radius.PacketServer{
|
server := radius.PacketServer{
|
||||||
@ -55,6 +64,7 @@ func handleAccessRequest(w radius.ResponseWriter, r *radius.Request) {
|
|||||||
username := rfc2865.UserName_GetString(r.Packet)
|
username := rfc2865.UserName_GetString(r.Packet)
|
||||||
password := rfc2865.UserPassword_GetString(r.Packet)
|
password := rfc2865.UserPassword_GetString(r.Packet)
|
||||||
organization := rfc2865.Class_GetString(r.Packet)
|
organization := rfc2865.Class_GetString(r.Packet)
|
||||||
|
state := rfc2865.State_GetString(r.Packet)
|
||||||
log.Printf("handleAccessRequest() username=%v, org=%v, password=%v", username, organization, password)
|
log.Printf("handleAccessRequest() username=%v, org=%v, password=%v", username, organization, password)
|
||||||
|
|
||||||
if organization == "" {
|
if organization == "" {
|
||||||
@ -62,12 +72,75 @@ func handleAccessRequest(w radius.ResponseWriter, r *radius.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := object.CheckUserPassword(organization, username, password, "en")
|
var user *object.User
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if state == "" {
|
||||||
|
user, err = object.CheckUserPassword(organization, username, password, "en")
|
||||||
|
} else {
|
||||||
|
user, err = object.GetUser(fmt.Sprintf("%s/%s", organization, username))
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.Write(r.Response(radius.CodeAccessReject))
|
w.Write(r.Response(radius.CodeAccessReject))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if user.IsMfaEnabled() {
|
||||||
|
mfaProp := user.GetMfaProps(object.TotpType, false)
|
||||||
|
if mfaProp == nil {
|
||||||
|
w.Write(r.Response(radius.CodeAccessReject))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if StateMap == nil {
|
||||||
|
StateMap = map[string]AccessStateContent{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if state != "" {
|
||||||
|
stateContent, ok := StateMap[state]
|
||||||
|
if !ok {
|
||||||
|
w.Write(r.Response(radius.CodeAccessReject))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(StateMap, state)
|
||||||
|
if stateContent.ExpiredAt.Before(time.Now()) {
|
||||||
|
w.Write(r.Response(radius.CodeAccessReject))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mfaUtil := object.GetMfaUtil(mfaProp.MfaType, mfaProp)
|
||||||
|
if mfaUtil.Verify(password) != nil {
|
||||||
|
w.Write(r.Response(radius.CodeAccessReject))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(r.Response(radius.CodeAccessAccept))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
responseState := util.GenerateId()
|
||||||
|
StateMap[responseState] = AccessStateContent{
|
||||||
|
time.Now().Add(StateExpiredTime),
|
||||||
|
}
|
||||||
|
|
||||||
|
err = rfc2865.State_Set(r.Packet, []byte(responseState))
|
||||||
|
if err != nil {
|
||||||
|
w.Write(r.Response(radius.CodeAccessReject))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = rfc2865.ReplyMessage_Set(r.Packet, []byte("please enter OTP"))
|
||||||
|
if err != nil {
|
||||||
|
w.Write(r.Response(radius.CodeAccessReject))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Packet.Code = radius.CodeAccessChallenge
|
||||||
|
w.Write(r.Packet)
|
||||||
|
}
|
||||||
|
|
||||||
w.Write(r.Response(radius.CodeAccessAccept))
|
w.Write(r.Response(radius.CodeAccessAccept))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,20 +35,13 @@ type Object struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getUsername(ctx *context.Context) (username string) {
|
func getUsername(ctx *context.Context) (username string) {
|
||||||
defer func() {
|
username, ok := ctx.Input.Session("username").(string)
|
||||||
if r := recover(); r != nil {
|
if !ok || username == "" {
|
||||||
username, _ = getUsernameByClientIdSecret(ctx)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
username = ctx.Input.Session("username").(string)
|
|
||||||
|
|
||||||
if username == "" {
|
|
||||||
username, _ = getUsernameByClientIdSecret(ctx)
|
username, _ = getUsernameByClientIdSecret(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
if username == "" {
|
if username == "" {
|
||||||
username = getUsernameByKeys(ctx)
|
username, _ = getUsernameByKeys(ctx)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -63,7 +56,7 @@ func getSubject(ctx *context.Context) (string, string) {
|
|||||||
return util.GetOwnerAndNameFromId(username)
|
return util.GetOwnerAndNameFromId(username)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getObject(ctx *context.Context) (string, string) {
|
func getObject(ctx *context.Context) (string, string, error) {
|
||||||
method := ctx.Request.Method
|
method := ctx.Request.Method
|
||||||
path := ctx.Request.URL.Path
|
path := ctx.Request.URL.Path
|
||||||
|
|
||||||
@ -72,13 +65,13 @@ func getObject(ctx *context.Context) (string, string) {
|
|||||||
if ctx.Input.Query("id") == "/" {
|
if ctx.Input.Query("id") == "/" {
|
||||||
adapterId := ctx.Input.Query("adapterId")
|
adapterId := ctx.Input.Query("adapterId")
|
||||||
if adapterId != "" {
|
if adapterId != "" {
|
||||||
return util.GetOwnerAndNameFromIdNoCheck(adapterId)
|
return util.GetOwnerAndNameFromIdWithError(adapterId)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// query == "?id=built-in/admin"
|
// query == "?id=built-in/admin"
|
||||||
id := ctx.Input.Query("id")
|
id := ctx.Input.Query("id")
|
||||||
if id != "" {
|
if id != "" {
|
||||||
return util.GetOwnerAndNameFromIdNoCheck(id)
|
return util.GetOwnerAndNameFromIdWithError(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -87,34 +80,34 @@ func getObject(ctx *context.Context) (string, string) {
|
|||||||
// query == "?id=built-in/admin"
|
// query == "?id=built-in/admin"
|
||||||
id := ctx.Input.Query("id")
|
id := ctx.Input.Query("id")
|
||||||
if id != "" {
|
if id != "" {
|
||||||
return util.GetOwnerAndNameFromIdNoCheck(id)
|
return util.GetOwnerAndNameFromIdWithError(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
owner := ctx.Input.Query("owner")
|
owner := ctx.Input.Query("owner")
|
||||||
if owner != "" {
|
if owner != "" {
|
||||||
return owner, ""
|
return owner, "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", ""
|
return "", "", nil
|
||||||
} else {
|
} else {
|
||||||
if path == "/api/add-policy" || path == "/api/remove-policy" || path == "/api/update-policy" {
|
if path == "/api/add-policy" || path == "/api/remove-policy" || path == "/api/update-policy" {
|
||||||
id := ctx.Input.Query("id")
|
id := ctx.Input.Query("id")
|
||||||
if id != "" {
|
if id != "" {
|
||||||
return util.GetOwnerAndNameFromIdNoCheck(id)
|
return util.GetOwnerAndNameFromIdWithError(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body := ctx.Input.RequestBody
|
body := ctx.Input.RequestBody
|
||||||
if len(body) == 0 {
|
if len(body) == 0 {
|
||||||
return ctx.Request.Form.Get("owner"), ctx.Request.Form.Get("name")
|
return ctx.Request.Form.Get("owner"), ctx.Request.Form.Get("name"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var obj Object
|
var obj Object
|
||||||
err := json.Unmarshal(body, &obj)
|
err := json.Unmarshal(body, &obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// panic(err)
|
// this is not error
|
||||||
return "", ""
|
return "", "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if path == "/api/delete-resource" {
|
if path == "/api/delete-resource" {
|
||||||
@ -124,7 +117,7 @@ func getObject(ctx *context.Context) (string, string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return obj.Owner, obj.Name
|
return obj.Owner, obj.Name, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,7 +183,12 @@ func ApiFilter(ctx *context.Context) {
|
|||||||
|
|
||||||
objOwner, objName := "", ""
|
objOwner, objName := "", ""
|
||||||
if urlPath != "/api/get-app-login" && urlPath != "/api/get-resource" {
|
if urlPath != "/api/get-app-login" && urlPath != "/api/get-resource" {
|
||||||
objOwner, objName = getObject(ctx)
|
var err error
|
||||||
|
objOwner, objName, err = getObject(ctx)
|
||||||
|
if err != nil {
|
||||||
|
responseError(ctx, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(urlPath, "/api/notify-payment") {
|
if strings.HasPrefix(urlPath, "/api/notify-payment") {
|
||||||
|
@ -16,6 +16,7 @@ package routers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/beego/beego/context"
|
"github.com/beego/beego/context"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
@ -23,6 +24,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func AutoSigninFilter(ctx *context.Context) {
|
func AutoSigninFilter(ctx *context.Context) {
|
||||||
|
urlPath := ctx.Request.URL.Path
|
||||||
|
if strings.HasPrefix(urlPath, "/api/login/oauth/access_token") {
|
||||||
|
return
|
||||||
|
}
|
||||||
//if getSessionUser(ctx) != "" {
|
//if getSessionUser(ctx) != "" {
|
||||||
// return
|
// return
|
||||||
//}
|
//}
|
||||||
@ -67,6 +72,17 @@ func AutoSigninFilter(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
accessKey := ctx.Input.Query("accessKey")
|
||||||
|
accessSecret := ctx.Input.Query("accessSecret")
|
||||||
|
if accessKey != "" && accessSecret != "" {
|
||||||
|
userId, err := getUsernameByKeys(ctx)
|
||||||
|
if err != nil {
|
||||||
|
responseError(ctx, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
setSessionUser(ctx, userId)
|
||||||
|
}
|
||||||
|
|
||||||
// "/page?clientId=123&clientSecret=456"
|
// "/page?clientId=123&clientSecret=456"
|
||||||
userId, err := getUsernameByClientIdSecret(ctx)
|
userId, err := getUsernameByClientIdSecret(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -91,17 +91,22 @@ func getUsernameByClientIdSecret(ctx *context.Context) (string, error) {
|
|||||||
return fmt.Sprintf("app/%s", application.Name), nil
|
return fmt.Sprintf("app/%s", application.Name), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUsernameByKeys(ctx *context.Context) string {
|
func getUsernameByKeys(ctx *context.Context) (string, error) {
|
||||||
accessKey, accessSecret := getKeys(ctx)
|
accessKey, accessSecret := getKeys(ctx)
|
||||||
user, err := object.GetUserByAccessKey(accessKey)
|
user, err := object.GetUserByAccessKey(accessKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if user != nil && accessSecret == user.AccessSecret {
|
if user == nil {
|
||||||
return user.GetId()
|
return "", fmt.Errorf("user not found for access key: %s", accessKey)
|
||||||
}
|
}
|
||||||
return ""
|
|
||||||
|
if accessSecret != user.AccessSecret {
|
||||||
|
return "", fmt.Errorf("incorrect access secret for user: %s", user.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return user.GetId(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSessionUser(ctx *context.Context) string {
|
func getSessionUser(ctx *context.Context) string {
|
||||||
|
@ -48,6 +48,10 @@ func CorsFilter(ctx *context.Context) {
|
|||||||
originHostname := getHostname(origin)
|
originHostname := getHostname(origin)
|
||||||
host := removePort(ctx.Request.Host)
|
host := removePort(ctx.Request.Host)
|
||||||
|
|
||||||
|
if origin == "null" {
|
||||||
|
origin = ""
|
||||||
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(origin, "http://localhost") || strings.HasPrefix(origin, "https://localhost") || strings.HasPrefix(origin, "http://127.0.0.1") || strings.HasPrefix(origin, "http://casdoor-app") || strings.Contains(origin, ".chromiumapp.org") {
|
if 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)
|
setCorsHeaders(ctx, origin)
|
||||||
return
|
return
|
||||||
|
@ -290,6 +290,7 @@ func initAPI() {
|
|||||||
|
|
||||||
beego.Router("/.well-known/openid-configuration", &controllers.RootController{}, "GET:GetOidcDiscovery")
|
beego.Router("/.well-known/openid-configuration", &controllers.RootController{}, "GET:GetOidcDiscovery")
|
||||||
beego.Router("/.well-known/jwks", &controllers.RootController{}, "*:GetJwks")
|
beego.Router("/.well-known/jwks", &controllers.RootController{}, "*:GetJwks")
|
||||||
|
beego.Router("/.well-known/webfinger", &controllers.RootController{}, "GET:GetWebFinger")
|
||||||
|
|
||||||
beego.Router("/cas/:organization/:application/serviceValidate", &controllers.RootController{}, "GET:CasServiceValidate")
|
beego.Router("/cas/:organization/:application/serviceValidate", &controllers.RootController{}, "GET:CasServiceValidate")
|
||||||
beego.Router("/cas/:organization/:application/proxyValidate", &controllers.RootController{}, "GET:CasProxyValidate")
|
beego.Router("/cas/:organization/:application/proxyValidate", &controllers.RootController{}, "GET:CasProxyValidate")
|
||||||
|
@ -43,6 +43,10 @@ func getWebBuildFolder() string {
|
|||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if util.FileExist(filepath.Join(frontendBaseDir, "index.html")) {
|
||||||
|
return frontendBaseDir
|
||||||
|
}
|
||||||
|
|
||||||
path = filepath.Join(frontendBaseDir, "web/build")
|
path = filepath.Join(frontendBaseDir, "web/build")
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
@ -58,7 +62,7 @@ func fastAutoSignin(ctx *context.Context) (string, error) {
|
|||||||
redirectUri := ctx.Input.Query("redirect_uri")
|
redirectUri := ctx.Input.Query("redirect_uri")
|
||||||
scope := ctx.Input.Query("scope")
|
scope := ctx.Input.Query("scope")
|
||||||
state := ctx.Input.Query("state")
|
state := ctx.Input.Query("state")
|
||||||
nonce := ""
|
nonce := ctx.Input.Query("nonce")
|
||||||
codeChallenge := ctx.Input.Query("code_challenge")
|
codeChallenge := ctx.Input.Query("code_challenge")
|
||||||
if clientId == "" || responseType != "code" || redirectUri == "" {
|
if clientId == "" || responseType != "code" || redirectUri == "" {
|
||||||
return "", nil
|
return "", nil
|
||||||
|
64
routers/timeout_filter.go
Normal file
64
routers/timeout_filter.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// Copyright 2024 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package routers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/beego/beego/context"
|
||||||
|
"github.com/casdoor/casdoor/conf"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
inactiveTimeoutMinutes int64
|
||||||
|
requestTimeMap sync.Map
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var err error
|
||||||
|
inactiveTimeoutMinutes, err = conf.GetConfigInt64("inactiveTimeoutMinutes")
|
||||||
|
if err != nil {
|
||||||
|
inactiveTimeoutMinutes = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func timeoutLogout(ctx *context.Context, sessionId string) {
|
||||||
|
requestTimeMap.Delete(sessionId)
|
||||||
|
ctx.Input.CruSession.Set("username", "")
|
||||||
|
ctx.Input.CruSession.Set("accessToken", "")
|
||||||
|
ctx.Input.CruSession.Delete("SessionData")
|
||||||
|
responseError(ctx, fmt.Sprintf(T(ctx, "auth:Timeout for inactivity of %d minutes"), inactiveTimeoutMinutes))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TimeoutFilter(ctx *context.Context) {
|
||||||
|
if inactiveTimeoutMinutes <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
owner, name := getSubject(ctx)
|
||||||
|
if owner == "anonymous" || name == "anonymous" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionId := ctx.Input.CruSession.SessionID()
|
||||||
|
currentTime := time.Now()
|
||||||
|
preRequestTime, has := requestTimeMap.Load(sessionId)
|
||||||
|
requestTimeMap.Store(sessionId, currentTime)
|
||||||
|
if has && preRequestTime.(time.Time).Add(time.Minute*time.Duration(inactiveTimeoutMinutes)).Before(currentTime) {
|
||||||
|
timeoutLogout(ctx, sessionId)
|
||||||
|
}
|
||||||
|
}
|
19
storage/casdoor.go
Normal file
19
storage/casdoor.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/casdoor/oss"
|
||||||
|
"github.com/casdoor/oss/casdoor"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewCasdoorStorageProvider(providerType string, clientId string, clientSecret string, region string, bucket string, endpoint string, cert string, content string) oss.StorageInterface {
|
||||||
|
sp := casdoor.New(&casdoor.Config{
|
||||||
|
clientId,
|
||||||
|
clientSecret,
|
||||||
|
endpoint,
|
||||||
|
cert,
|
||||||
|
region,
|
||||||
|
content,
|
||||||
|
bucket,
|
||||||
|
})
|
||||||
|
return sp
|
||||||
|
}
|
@ -16,7 +16,7 @@ package storage
|
|||||||
|
|
||||||
import "github.com/casdoor/oss"
|
import "github.com/casdoor/oss"
|
||||||
|
|
||||||
func GetStorageProvider(providerType string, clientId string, clientSecret string, region string, bucket string, endpoint string) (oss.StorageInterface, error) {
|
func GetStorageProvider(providerType string, clientId string, clientSecret string, region string, bucket string, endpoint string, cert string, content string) (oss.StorageInterface, error) {
|
||||||
switch providerType {
|
switch providerType {
|
||||||
case "Local File System":
|
case "Local File System":
|
||||||
return NewLocalFileSystemStorageProvider(), nil
|
return NewLocalFileSystemStorageProvider(), nil
|
||||||
@ -36,6 +36,8 @@ func GetStorageProvider(providerType string, clientId string, clientSecret strin
|
|||||||
return NewGoogleCloudStorageProvider(clientSecret, bucket, endpoint), nil
|
return NewGoogleCloudStorageProvider(clientSecret, bucket, endpoint), nil
|
||||||
case "Synology":
|
case "Synology":
|
||||||
return NewSynologyNasStorageProvider(clientId, clientSecret, endpoint), nil
|
return NewSynologyNasStorageProvider(clientId, clientSecret, endpoint), nil
|
||||||
|
case "Casdoor":
|
||||||
|
return NewCasdoorStorageProvider(providerType, clientId, clientSecret, region, bucket, endpoint, cert, content), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
76
util/obfuscator.go
Normal file
76
util/obfuscator.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
// Copyright 2024 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/des"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func unPaddingPkcs7(s []byte) []byte {
|
||||||
|
length := len(s)
|
||||||
|
if length == 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
unPadding := int(s[length-1])
|
||||||
|
return s[:(length - unPadding)]
|
||||||
|
}
|
||||||
|
|
||||||
|
func decryptDesOrAes(passwordCipher string, block cipher.Block) (string, error) {
|
||||||
|
passwordCipherBytes, err := hex.DecodeString(passwordCipher)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(passwordCipherBytes) < block.BlockSize() {
|
||||||
|
return "", fmt.Errorf("the password ciphertext should contain a random hexadecimal string of length %d at the beginning", block.BlockSize()*2)
|
||||||
|
}
|
||||||
|
|
||||||
|
iv := passwordCipherBytes[:block.BlockSize()]
|
||||||
|
password := make([]byte, len(passwordCipherBytes)-block.BlockSize())
|
||||||
|
|
||||||
|
mode := cipher.NewCBCDecrypter(block, iv)
|
||||||
|
mode.CryptBlocks(password, passwordCipherBytes[block.BlockSize():])
|
||||||
|
|
||||||
|
return string(unPaddingPkcs7(password)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUnobfuscatedPassword(passwordObfuscatorType string, passwordObfuscatorKey string, passwordCipher string) (string, error) {
|
||||||
|
if passwordObfuscatorType == "Plain" || passwordObfuscatorType == "" {
|
||||||
|
return passwordCipher, nil
|
||||||
|
} else if passwordObfuscatorType == "DES" || passwordObfuscatorType == "AES" {
|
||||||
|
key, err := hex.DecodeString(passwordObfuscatorKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var block cipher.Block
|
||||||
|
if passwordObfuscatorType == "DES" {
|
||||||
|
block, err = des.NewCipher(key)
|
||||||
|
} else {
|
||||||
|
block, err = aes.NewCipher(key)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return decryptDesOrAes(passwordCipher, block)
|
||||||
|
} else {
|
||||||
|
return "", fmt.Errorf("unsupported password obfuscator type: %s", passwordObfuscatorType)
|
||||||
|
}
|
||||||
|
}
|
@ -131,6 +131,15 @@ func GetOwnerAndNameFromId(id string) (string, string) {
|
|||||||
return tokens[0], tokens[1]
|
return tokens[0], tokens[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetOwnerAndNameFromIdWithError(id string) (string, string, error) {
|
||||||
|
tokens := strings.Split(id, "/")
|
||||||
|
if len(tokens) != 2 {
|
||||||
|
return "", "", errors.New("GetOwnerAndNameFromId() error, wrong token count for ID: " + id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokens[0], tokens[1], nil
|
||||||
|
}
|
||||||
|
|
||||||
func GetOwnerFromId(id string) string {
|
func GetOwnerFromId(id string) string {
|
||||||
tokens := strings.Split(id, "/")
|
tokens := strings.Split(id, "/")
|
||||||
if len(tokens) != 2 {
|
if len(tokens) != 2 {
|
||||||
@ -154,6 +163,16 @@ func GetOwnerAndNameAndOtherFromId(id string) (string, string, string) {
|
|||||||
return tokens[0], tokens[1], tokens[2]
|
return tokens[0], tokens[1], tokens[2]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetSharedOrgFromApp(rawName string) (name string, organization string) {
|
||||||
|
name = rawName
|
||||||
|
splitName := strings.Split(rawName, "-org-")
|
||||||
|
if len(splitName) >= 2 {
|
||||||
|
organization = splitName[len(splitName)-1]
|
||||||
|
name = splitName[0]
|
||||||
|
}
|
||||||
|
return name, organization
|
||||||
|
}
|
||||||
|
|
||||||
func GenerateId() string {
|
func GenerateId() string {
|
||||||
return uuid.NewString()
|
return uuid.NewString()
|
||||||
}
|
}
|
||||||
@ -354,9 +373,16 @@ func StringToInterfaceArray(array []string) []interface{} {
|
|||||||
func StringToInterfaceArray2d(arrays [][]string) [][]interface{} {
|
func StringToInterfaceArray2d(arrays [][]string) [][]interface{} {
|
||||||
var interfaceArrays [][]interface{}
|
var interfaceArrays [][]interface{}
|
||||||
for _, req := range arrays {
|
for _, req := range arrays {
|
||||||
var interfaceArray []interface{}
|
var (
|
||||||
for _, r := range req {
|
interfaceArray []interface{}
|
||||||
interfaceArray = append(interfaceArray, r)
|
elem interface{}
|
||||||
|
)
|
||||||
|
for _, elem = range req {
|
||||||
|
jStruct, err := TryJsonToAnonymousStruct(elem.(string))
|
||||||
|
if err == nil {
|
||||||
|
elem = jStruct
|
||||||
|
}
|
||||||
|
interfaceArray = append(interfaceArray, elem)
|
||||||
}
|
}
|
||||||
interfaceArrays = append(interfaceArrays, interfaceArray)
|
interfaceArrays = append(interfaceArrays, interfaceArray)
|
||||||
}
|
}
|
||||||
|
@ -51,6 +51,9 @@ func IsPhoneValid(phone string, countryCode string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func IsPhoneAllowInRegin(countryCode string, allowRegions []string) bool {
|
func IsPhoneAllowInRegin(countryCode string, allowRegions []string) bool {
|
||||||
|
if ContainsString(allowRegions, "All") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
return ContainsString(allowRegions, countryCode)
|
return ContainsString(allowRegions, countryCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
"copy-to-clipboard": "^3.3.1",
|
"copy-to-clipboard": "^3.3.1",
|
||||||
"core-js": "^3.25.0",
|
"core-js": "^3.25.0",
|
||||||
"craco-less": "^2.0.0",
|
"craco-less": "^2.0.0",
|
||||||
|
"crypto-js": "^4.2.0",
|
||||||
"echarts": "^5.4.3",
|
"echarts": "^5.4.3",
|
||||||
"ethers": "5.6.9",
|
"ethers": "5.6.9",
|
||||||
"face-api.js": "^0.22.2",
|
"face-api.js": "^0.22.2",
|
||||||
|
@ -252,8 +252,8 @@ class AdapterEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("provider:DB test"), i18next.t("provider:DB test - Tooltip"))} :
|
{Setting.getLabel(i18next.t("provider:DB test"), i18next.t("provider:DB test - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={2} >
|
<Col span={2} >
|
||||||
<Button type={"primary"} onClick={() => {
|
<Button disabled={this.state.organizationName !== this.state.adapter.owner} type={"primary"} onClick={() => {
|
||||||
AdapterBackend.getPolicies("", "", `${this.state.organizationName}/${this.state.adapterName}`)
|
AdapterBackend.getPolicies("", "", `${this.state.adapter.owner}/${this.state.adapter.name}`)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("syncer:Connect successfully"));
|
Setting.showMessage("success", i18next.t("syncer:Connect successfully"));
|
||||||
@ -279,13 +279,14 @@ class AdapterEditPage extends React.Component {
|
|||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully saved"));
|
Setting.showMessage("success", i18next.t("general:Successfully saved"));
|
||||||
this.setState({
|
this.setState({
|
||||||
|
organizationName: this.state.adapter.owner,
|
||||||
adapterName: this.state.adapter.name,
|
adapterName: this.state.adapter.name,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (exitAfterSave) {
|
if (exitAfterSave) {
|
||||||
this.props.history.push("/adapters");
|
this.props.history.push("/adapters");
|
||||||
} else {
|
} else {
|
||||||
this.props.history.push(`/adapters/${this.state.organizationName}/${this.state.adapter.name}`);
|
this.props.history.push(`/adapters/${this.state.adapter.owner}/${this.state.adapter.name}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
|
||||||
|
@ -56,9 +56,11 @@ class AdapterListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -16,6 +16,7 @@ import React, {Component, Suspense, lazy} from "react";
|
|||||||
import "./App.less";
|
import "./App.less";
|
||||||
import {Helmet} from "react-helmet";
|
import {Helmet} from "react-helmet";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
|
import {setOrgIsTourVisible, setTourLogo} from "./TourConfig";
|
||||||
import {StyleProvider, legacyLogicalPropertiesTransformer} from "@ant-design/cssinjs";
|
import {StyleProvider, legacyLogicalPropertiesTransformer} from "@ant-design/cssinjs";
|
||||||
import {GithubOutlined, InfoCircleFilled, ShareAltOutlined} from "@ant-design/icons";
|
import {GithubOutlined, InfoCircleFilled, ShareAltOutlined} from "@ant-design/icons";
|
||||||
import {Alert, Button, ConfigProvider, Drawer, FloatButton, Layout, Result, Tooltip} from "antd";
|
import {Alert, Button, ConfigProvider, Drawer, FloatButton, Layout, Result, Tooltip} from "antd";
|
||||||
@ -247,6 +248,8 @@ class App extends Component {
|
|||||||
|
|
||||||
this.setLanguage(account);
|
this.setLanguage(account);
|
||||||
this.setTheme(Setting.getThemeData(account.organization), Conf.InitThemeAlgorithm);
|
this.setTheme(Setting.getThemeData(account.organization), Conf.InitThemeAlgorithm);
|
||||||
|
setTourLogo(account.organization.logo);
|
||||||
|
setOrgIsTourVisible(account.organization.enableTour);
|
||||||
} else {
|
} else {
|
||||||
if (res.data !== "Please login first") {
|
if (res.data !== "Please login first") {
|
||||||
Setting.showMessage("error", `${i18next.t("application:Failed to sign in")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("application:Failed to sign in")}: ${res.msg}`);
|
||||||
@ -341,7 +344,8 @@ class App extends Component {
|
|||||||
window.location.pathname.startsWith("/cas") ||
|
window.location.pathname.startsWith("/cas") ||
|
||||||
window.location.pathname.startsWith("/select-plan") ||
|
window.location.pathname.startsWith("/select-plan") ||
|
||||||
window.location.pathname.startsWith("/buy-plan") ||
|
window.location.pathname.startsWith("/buy-plan") ||
|
||||||
window.location.pathname.startsWith("/qrcode") ;
|
window.location.pathname.startsWith("/qrcode") ||
|
||||||
|
window.location.pathname.startsWith("/captcha");
|
||||||
}
|
}
|
||||||
|
|
||||||
onClick = ({key}) => {
|
onClick = ({key}) => {
|
||||||
|
@ -116,7 +116,6 @@ class ApplicationEditPage extends React.Component {
|
|||||||
UNSAFE_componentWillMount() {
|
UNSAFE_componentWillMount() {
|
||||||
this.getApplication();
|
this.getApplication();
|
||||||
this.getOrganizations();
|
this.getOrganizations();
|
||||||
this.getProviders();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getApplication() {
|
getApplication() {
|
||||||
@ -145,7 +144,9 @@ class ApplicationEditPage extends React.Component {
|
|||||||
application: application,
|
application: application,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.getCerts(application.organization);
|
this.getProviders(application);
|
||||||
|
|
||||||
|
this.getCerts(application);
|
||||||
|
|
||||||
this.getSamlMetadata(application.enableSamlPostBinding);
|
this.getSamlMetadata(application.enableSamlPostBinding);
|
||||||
});
|
});
|
||||||
@ -166,7 +167,11 @@ class ApplicationEditPage extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getCerts(owner) {
|
getCerts(application) {
|
||||||
|
let owner = application.organization;
|
||||||
|
if (application.isShared) {
|
||||||
|
owner = this.props.owner;
|
||||||
|
}
|
||||||
CertBackend.getCerts(owner)
|
CertBackend.getCerts(owner)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
@ -175,8 +180,12 @@ class ApplicationEditPage extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getProviders() {
|
getProviders(application) {
|
||||||
ProviderBackend.getProviders(this.state.owner)
|
let owner = application.organization;
|
||||||
|
if (application.isShared) {
|
||||||
|
owner = this.props.account.owner;
|
||||||
|
}
|
||||||
|
ProviderBackend.getProviders(owner)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
this.setState({
|
this.setState({
|
||||||
@ -263,6 +272,16 @@ class ApplicationEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Is shared"), i18next.t("general:Is shared - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Switch disabled={Setting.isAdminUser()} checked={this.state.application.isShared} onChange={checked => {
|
||||||
|
this.updateApplicationField("isShared", checked);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("general:Logo"), i18next.t("general:Logo - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Logo"), i18next.t("general:Logo - Tooltip"))} :
|
||||||
@ -384,7 +403,17 @@ class ApplicationEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Select virtual={false} style={{width: "100%"}} value={this.state.application.tokenFormat} onChange={(value => {this.updateApplicationField("tokenFormat", value);})}
|
<Select virtual={false} style={{width: "100%"}} value={this.state.application.tokenFormat} onChange={(value => {this.updateApplicationField("tokenFormat", value);})}
|
||||||
options={["JWT", "JWT-Empty", "JWT-Custom"].map((item) => Setting.getOption(item, item))}
|
options={["JWT", "JWT-Empty", "JWT-Custom", "JWT-Standard"].map((item) => Setting.getOption(item, item))}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("application:Token signing method"), i18next.t("application:Token signing method - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: "100%"}} value={this.state.application.tokenSigningMethod === "" ? "RS256" : this.state.application.tokenSigningMethod} onChange={(value => {this.updateApplicationField("tokenSigningMethod", value);})}
|
||||||
|
options={["RS256", "RS512", "ES256", "ES512", "ES384"].map((item) => Setting.getOption(item, item))}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
@ -674,6 +703,16 @@ class ApplicationEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}}>
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("application:Use Email as NameID"), i18next.t("application:Use Email as NameID - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={1}>
|
||||||
|
<Switch checked={this.state.application.useEmailAsSamlNameId} onChange={checked => {
|
||||||
|
this.updateApplicationField("useEmailAsSamlNameId", checked);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||||
{Setting.getLabel(i18next.t("application:Enable SAML POST binding"), i18next.t("application:Enable SAML POST binding - Tooltip"))} :
|
{Setting.getLabel(i18next.t("application:Enable SAML POST binding"), i18next.t("application:Enable SAML POST binding - Tooltip"))} :
|
||||||
@ -989,7 +1028,11 @@ class ApplicationEditPage extends React.Component {
|
|||||||
redirectUri = "\"ERROR: You must specify at least one Redirect URL in 'Redirect URLs'\"";
|
redirectUri = "\"ERROR: You must specify at least one Redirect URL in 'Redirect URLs'\"";
|
||||||
}
|
}
|
||||||
|
|
||||||
const signInUrl = `/login/oauth/authorize?client_id=${this.state.application.clientId}&response_type=code&redirect_uri=${redirectUri}&scope=read&state=casdoor`;
|
let clientId = this.state.application.clientId;
|
||||||
|
if (this.state.application.isShared) {
|
||||||
|
clientId += `-org-${this.props.account.owner}`;
|
||||||
|
}
|
||||||
|
const signInUrl = `/login/oauth/authorize?client_id=${clientId}&response_type=code&redirect_uri=${redirectUri}&scope=read&state=casdoor`;
|
||||||
const maskStyle = {position: "absolute", top: "0px", left: "0px", zIndex: 10, height: "97%", width: "100%", background: "rgba(0,0,0,0.4)"};
|
const maskStyle = {position: "absolute", top: "0px", left: "0px", zIndex: 10, height: "97%", width: "100%", background: "rgba(0,0,0,0.4)"};
|
||||||
if (!Setting.isPasswordEnabled(this.state.application)) {
|
if (!Setting.isPasswordEnabled(this.state.application)) {
|
||||||
signUpUrl = signInUrl.replace("/login/oauth/authorize", "/signup/oauth/authorize");
|
signUpUrl = signInUrl.replace("/login/oauth/authorize", "/signup/oauth/authorize");
|
||||||
|
@ -97,9 +97,11 @@ class ApplicationListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
@ -123,7 +125,7 @@ class ApplicationListPage extends BaseListPage {
|
|||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return (
|
return (
|
||||||
<Link to={`/applications/${record.organization}/${text}`}>
|
<Link to={`/applications/${record.organization}/${text}`}>
|
||||||
{text}
|
{Setting.getApplicationDisplayName(record)}
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
116
web/src/CaptchaPage.js
Normal file
116
web/src/CaptchaPage.js
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import {CaptchaModal} from "./common/modal/CaptchaModal";
|
||||||
|
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
||||||
|
import * as Setting from "./Setting";
|
||||||
|
|
||||||
|
class CaptchaPage extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
const params = new URLSearchParams(this.props.location.search);
|
||||||
|
this.state = {
|
||||||
|
owner: "admin",
|
||||||
|
application: null,
|
||||||
|
clientId: params.get("client_id"),
|
||||||
|
applicationName: params.get("state"),
|
||||||
|
redirectUri: params.get("redirect_uri"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.getApplication();
|
||||||
|
}
|
||||||
|
|
||||||
|
onUpdateApplication(application) {
|
||||||
|
this.setState({
|
||||||
|
application: application,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getApplication() {
|
||||||
|
if (this.state.applicationName === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplicationBackend.getApplication(this.state.owner, this.state.applicationName)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "error") {
|
||||||
|
this.onUpdateApplication(null);
|
||||||
|
this.setState({
|
||||||
|
msg: res.msg,
|
||||||
|
});
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
this.onUpdateApplication(res.data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getCaptchaProviderItems(application) {
|
||||||
|
const providers = application?.providers;
|
||||||
|
|
||||||
|
if (providers === undefined || providers === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return providers.filter(providerItem => {
|
||||||
|
if (providerItem.provider === undefined || providerItem.provider === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return providerItem.provider.category === "Captcha";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(values) {
|
||||||
|
Setting.goToLink(`${this.state.redirectUri}?code=${values.captchaToken}&type=${values.captchaType}&secret=${values.clientSecret}&applicationId=${values.applicationId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCaptchaModal(application) {
|
||||||
|
const captchaProviderItems = this.getCaptchaProviderItems(application);
|
||||||
|
if (captchaProviderItems === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const alwaysProviderItems = captchaProviderItems.filter(providerItem => providerItem.rule === "Always");
|
||||||
|
const dynamicProviderItems = captchaProviderItems.filter(providerItem => providerItem.rule === "Dynamic");
|
||||||
|
const provider = alwaysProviderItems.length > 0
|
||||||
|
? alwaysProviderItems[0].provider
|
||||||
|
: dynamicProviderItems[0].provider;
|
||||||
|
|
||||||
|
return <CaptchaModal
|
||||||
|
owner={provider.owner}
|
||||||
|
name={provider.name}
|
||||||
|
visible={true}
|
||||||
|
onOk={(captchaType, captchaToken, clientSecret) => {
|
||||||
|
const values = {
|
||||||
|
captchaType: captchaType,
|
||||||
|
captchaToken: captchaToken,
|
||||||
|
clientSecret: clientSecret,
|
||||||
|
applicationId: `${provider.owner}/${provider.name}`,
|
||||||
|
};
|
||||||
|
this.callback(values);
|
||||||
|
}}
|
||||||
|
onCancel={() => this.callback({captchaType: "none", captchaToken: "", clientSecret: ""})}
|
||||||
|
isCurrentProvider={true}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
this.renderCaptchaModal(this.state.application)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CaptchaPage;
|
97
web/src/CasbinEditor.js
Normal file
97
web/src/CasbinEditor.js
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
// Copyright 2024 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import React, {useCallback, useEffect, useRef, useState} from "react";
|
||||||
|
import {Controlled as CodeMirror} from "react-codemirror2";
|
||||||
|
import "codemirror/lib/codemirror.css";
|
||||||
|
import "codemirror/mode/properties/properties";
|
||||||
|
import * as Setting from "./Setting";
|
||||||
|
import IframeEditor from "./IframeEditor";
|
||||||
|
import {Tabs} from "antd";
|
||||||
|
|
||||||
|
const {TabPane} = Tabs;
|
||||||
|
|
||||||
|
const CasbinEditor = ({model, onModelTextChange}) => {
|
||||||
|
const [activeKey, setActiveKey] = useState("advanced");
|
||||||
|
const iframeRef = useRef(null);
|
||||||
|
const [localModelText, setLocalModelText] = useState(model.modelText);
|
||||||
|
|
||||||
|
const handleModelTextChange = useCallback((newModelText) => {
|
||||||
|
if (!Setting.builtInObject(model)) {
|
||||||
|
setLocalModelText(newModelText);
|
||||||
|
onModelTextChange(newModelText);
|
||||||
|
}
|
||||||
|
}, [model, onModelTextChange]);
|
||||||
|
|
||||||
|
const syncModelText = useCallback(() => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (activeKey === "advanced" && iframeRef.current) {
|
||||||
|
const handleSyncMessage = (event) => {
|
||||||
|
if (event.data.type === "modelUpdate") {
|
||||||
|
window.removeEventListener("message", handleSyncMessage);
|
||||||
|
handleModelTextChange(event.data.modelText);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener("message", handleSyncMessage);
|
||||||
|
iframeRef.current.getModelText();
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [activeKey, handleModelTextChange]);
|
||||||
|
|
||||||
|
const handleTabChange = (key) => {
|
||||||
|
syncModelText().then(() => {
|
||||||
|
setActiveKey(key);
|
||||||
|
if (key === "advanced" && iframeRef.current) {
|
||||||
|
iframeRef.current.updateModelText(localModelText);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLocalModelText(model.modelText);
|
||||||
|
}, [model.modelText]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{height: "100%", width: "100%", display: "flex", flexDirection: "column"}}>
|
||||||
|
<Tabs activeKey={activeKey} onChange={handleTabChange} style={{flex: "0 0 auto", marginTop: "-10px"}}>
|
||||||
|
<TabPane tab="Basic Editor" key="basic" />
|
||||||
|
<TabPane tab="Advanced Editor" key="advanced" />
|
||||||
|
</Tabs>
|
||||||
|
<div style={{flex: "1 1 auto", overflow: "hidden"}}>
|
||||||
|
{activeKey === "advanced" ? (
|
||||||
|
<IframeEditor
|
||||||
|
ref={iframeRef}
|
||||||
|
initialModelText={localModelText}
|
||||||
|
onModelTextChange={handleModelTextChange}
|
||||||
|
style={{width: "100%", height: "100%"}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<CodeMirror
|
||||||
|
value={localModelText}
|
||||||
|
className="full-height-editor no-horizontal-scroll-editor"
|
||||||
|
options={{mode: "properties", theme: "default"}}
|
||||||
|
onBeforeChange={(editor, data, value) => {
|
||||||
|
handleModelTextChange(value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CasbinEditor;
|
@ -288,14 +288,14 @@ class CertEditPage extends React.Component {
|
|||||||
Setting.showMessage("success", i18next.t("general:Successfully saved"));
|
Setting.showMessage("success", i18next.t("general:Successfully saved"));
|
||||||
this.setState({
|
this.setState({
|
||||||
certName: this.state.cert.name,
|
certName: this.state.cert.name,
|
||||||
|
}, () => {
|
||||||
|
if (exitAfterSave) {
|
||||||
|
this.props.history.push("/certs");
|
||||||
|
} else {
|
||||||
|
this.props.history.push(`/certs/${this.state.cert.owner}/${this.state.cert.name}`);
|
||||||
|
this.getCert();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (exitAfterSave) {
|
|
||||||
this.props.history.push("/certs");
|
|
||||||
} else {
|
|
||||||
this.props.history.push(`/certs/${this.state.cert.owner}/${this.state.cert.name}`);
|
|
||||||
this.getCert();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
|
||||||
this.updateCertField("name", this.state.certName);
|
this.updateCertField("name", this.state.certName);
|
||||||
|
@ -73,9 +73,11 @@ class CertListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -55,9 +55,11 @@ class EnforcerListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -32,6 +32,7 @@ import {authConfig} from "./auth/Auth";
|
|||||||
import ProductBuyPage from "./ProductBuyPage";
|
import ProductBuyPage from "./ProductBuyPage";
|
||||||
import PaymentResultPage from "./PaymentResultPage";
|
import PaymentResultPage from "./PaymentResultPage";
|
||||||
import QrCodePage from "./QrCodePage";
|
import QrCodePage from "./QrCodePage";
|
||||||
|
import CaptchaPage from "./CaptchaPage";
|
||||||
import CustomHead from "./basic/CustomHead";
|
import CustomHead from "./basic/CustomHead";
|
||||||
|
|
||||||
class EntryPage extends React.Component {
|
class EntryPage extends React.Component {
|
||||||
@ -120,6 +121,7 @@ class EntryPage extends React.Component {
|
|||||||
<Route exact path="/buy-plan/:owner/:pricingName" render={(props) => <ProductBuyPage {...this.props} pricing={this.state.pricing} onUpdatePricing={onUpdatePricing} {...props} />} />
|
<Route exact path="/buy-plan/:owner/:pricingName" render={(props) => <ProductBuyPage {...this.props} pricing={this.state.pricing} onUpdatePricing={onUpdatePricing} {...props} />} />
|
||||||
<Route exact path="/buy-plan/:owner/:pricingName/result" render={(props) => <PaymentResultPage {...this.props} pricing={this.state.pricing} onUpdatePricing={onUpdatePricing} {...props} />} />
|
<Route exact path="/buy-plan/:owner/:pricingName/result" render={(props) => <PaymentResultPage {...this.props} pricing={this.state.pricing} onUpdatePricing={onUpdatePricing} {...props} />} />
|
||||||
<Route exact path="/qrcode/:owner/:paymentName" render={(props) => <QrCodePage {...this.props} onUpdateApplication={onUpdateApplication} {...props} />} />
|
<Route exact path="/qrcode/:owner/:paymentName" render={(props) => <QrCodePage {...this.props} onUpdateApplication={onUpdateApplication} {...props} />} />
|
||||||
|
<Route exact path="/captcha" render={(props) => <CaptchaPage {...props} />} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
@ -84,9 +84,11 @@ class GroupListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
66
web/src/IframeEditor.js
Normal file
66
web/src/IframeEditor.js
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
// Copyright 2024 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import React, {forwardRef, useEffect, useImperativeHandle, useRef, useState} from "react";
|
||||||
|
|
||||||
|
const IframeEditor = forwardRef(({initialModelText, onModelTextChange}, ref) => {
|
||||||
|
const iframeRef = useRef(null);
|
||||||
|
const [iframeReady, setIframeReady] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleMessage = (event) => {
|
||||||
|
if (event.origin !== "https://editor.casbin.org") {return;}
|
||||||
|
|
||||||
|
if (event.data.type === "modelUpdate") {
|
||||||
|
onModelTextChange(event.data.modelText);
|
||||||
|
} else if (event.data.type === "iframeReady") {
|
||||||
|
setIframeReady(true);
|
||||||
|
iframeRef.current?.contentWindow.postMessage({
|
||||||
|
type: "initializeModel",
|
||||||
|
modelText: initialModelText,
|
||||||
|
}, "*");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("message", handleMessage);
|
||||||
|
return () => window.removeEventListener("message", handleMessage);
|
||||||
|
}, [onModelTextChange, initialModelText]);
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
getModelText: () => {
|
||||||
|
iframeRef.current?.contentWindow.postMessage({type: "getModelText"}, "*");
|
||||||
|
},
|
||||||
|
updateModelText: (newModelText) => {
|
||||||
|
if (iframeReady) {
|
||||||
|
iframeRef.current?.contentWindow.postMessage({
|
||||||
|
type: "updateModelText",
|
||||||
|
modelText: newModelText,
|
||||||
|
}, "*");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<iframe
|
||||||
|
ref={iframeRef}
|
||||||
|
src="https://editor.casbin.org/model-editor"
|
||||||
|
frameBorder="0"
|
||||||
|
width="100%"
|
||||||
|
height="500px"
|
||||||
|
title="Casbin Model Editor"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default IframeEditor;
|
@ -20,6 +20,7 @@ import * as ApplicationBackend from "./backend/ApplicationBackend";
|
|||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import copy from "copy-to-clipboard";
|
import copy from "copy-to-clipboard";
|
||||||
|
import * as GroupBackend from "./backend/GroupBackend";
|
||||||
|
|
||||||
const {Option} = Select;
|
const {Option} = Select;
|
||||||
|
|
||||||
@ -33,6 +34,7 @@ class InvitationEditPage extends React.Component {
|
|||||||
invitation: null,
|
invitation: null,
|
||||||
organizations: [],
|
organizations: [],
|
||||||
applications: [],
|
applications: [],
|
||||||
|
groups: [],
|
||||||
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -41,6 +43,7 @@ class InvitationEditPage extends React.Component {
|
|||||||
this.getInvitation();
|
this.getInvitation();
|
||||||
this.getOrganizations();
|
this.getOrganizations();
|
||||||
this.getApplicationsByOrganization(this.state.organizationName);
|
this.getApplicationsByOrganization(this.state.organizationName);
|
||||||
|
this.getGroupsByOrganization(this.state.organizationName);
|
||||||
}
|
}
|
||||||
|
|
||||||
getInvitation() {
|
getInvitation() {
|
||||||
@ -75,6 +78,17 @@ class InvitationEditPage extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getGroupsByOrganization(organizationName) {
|
||||||
|
GroupBackend.getGroups(organizationName)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
this.setState({
|
||||||
|
groups: res.data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
parseInvitationField(key, value) {
|
parseInvitationField(key, value) {
|
||||||
if ([""].includes(key)) {
|
if ([""].includes(key)) {
|
||||||
value = Setting.myParseInt(value);
|
value = Setting.myParseInt(value);
|
||||||
@ -120,7 +134,7 @@ class InvitationEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Select virtual={false} style={{width: "100%"}} disabled={!Setting.isAdminUser(this.props.account) || isCreatedByPlan} value={this.state.invitation.owner} onChange={(value => {this.updateInvitationField("owner", value); this.getApplicationsByOrganization(value);})}>
|
<Select virtual={false} style={{width: "100%"}} disabled={!Setting.isAdminUser(this.props.account) || isCreatedByPlan} value={this.state.invitation.owner} onChange={(value => {this.updateInvitationField("owner", value); this.getApplicationsByOrganization(value);this.getGroupsByOrganization(value);})}>
|
||||||
{
|
{
|
||||||
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
|
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
|
||||||
}
|
}
|
||||||
@ -199,11 +213,26 @@ class InvitationEditPage extends React.Component {
|
|||||||
<Select virtual={false} style={{width: "100%"}} value={this.state.invitation.application}
|
<Select virtual={false} style={{width: "100%"}} value={this.state.invitation.application}
|
||||||
onChange={(value => {this.updateInvitationField("application", value);})}
|
onChange={(value => {this.updateInvitationField("application", value);})}
|
||||||
options={[
|
options={[
|
||||||
{label: "All", value: i18next.t("general:All")},
|
{label: i18next.t("general:All"), value: "All"},
|
||||||
...this.state.applications.map((application) => Setting.getOption(application.name, application.name)),
|
...this.state.applications.map((application) => Setting.getOption(application.name, application.name)),
|
||||||
]} />
|
]} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("provider:Signup group"), i18next.t("provider:Signup group - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: "100%"}} value={this.state.invitation.signupGroup} onChange={(value => {this.updateInvitationField("signupGroup", value);})}>
|
||||||
|
<Option key={""} value={""}>
|
||||||
|
{i18next.t("general:Default")}
|
||||||
|
</Option>
|
||||||
|
{
|
||||||
|
this.state.groups.map((group, index) => <Option key={index} value={`${group.owner}/${group.name}`}>{group.name}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("signup:Username"), i18next.t("signup:Username - Tooltip"))} :
|
{Setting.getLabel(i18next.t("signup:Username"), i18next.t("signup:Username - Tooltip"))} :
|
||||||
|
@ -68,9 +68,11 @@ class InvitationListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -13,12 +13,13 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Button, Card, Col, Input, InputNumber, Row, Select, Switch} from "antd";
|
import {Button, Card, Col, Input, InputNumber, Row, Select, Space, Switch} from "antd";
|
||||||
import {EyeInvisibleOutlined, EyeTwoTone} from "@ant-design/icons";
|
import {EyeInvisibleOutlined, EyeTwoTone, HolderOutlined, UsergroupAddOutlined} from "@ant-design/icons";
|
||||||
import * as LddpBackend from "./backend/LdapBackend";
|
import * as LddpBackend from "./backend/LdapBackend";
|
||||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import * as GroupBackend from "./backend/GroupBackend";
|
||||||
|
|
||||||
const {Option} = Select;
|
const {Option} = Select;
|
||||||
|
|
||||||
@ -30,12 +31,14 @@ class LdapEditPage extends React.Component {
|
|||||||
organizationName: props.match.params.organizationName,
|
organizationName: props.match.params.organizationName,
|
||||||
ldap: null,
|
ldap: null,
|
||||||
organizations: [],
|
organizations: [],
|
||||||
|
groups: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
UNSAFE_componentWillMount() {
|
UNSAFE_componentWillMount() {
|
||||||
this.getLdap();
|
this.getLdap();
|
||||||
this.getOrganizations();
|
this.getOrganizations();
|
||||||
|
this.getGroups();
|
||||||
}
|
}
|
||||||
|
|
||||||
getLdap() {
|
getLdap() {
|
||||||
@ -60,6 +63,17 @@ class LdapEditPage extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getGroups() {
|
||||||
|
GroupBackend.getGroups(this.state.organizationName)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
this.setState({
|
||||||
|
groups: res.data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
updateLdapField(key, value) {
|
updateLdapField(key, value) {
|
||||||
this.setState((prevState) => {
|
this.setState((prevState) => {
|
||||||
prevState.ldap[key] = value;
|
prevState.ldap[key] = value;
|
||||||
@ -214,6 +228,31 @@ class LdapEditPage extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{lineHeight: "32px", textAlign: "right", paddingRight: "25px"}} span={3}>
|
||||||
|
{Setting.getLabel(i18next.t("ldap:Default group"), i18next.t("ldap:Default group - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={21}>
|
||||||
|
<Select virtual={false} style={{width: "100%"}} value={this.state.ldap.defaultGroup ?? []} onChange={(value => {
|
||||||
|
this.updateLdapField("defaultGroup", value);
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Option key={""} value={""}>
|
||||||
|
<Space>
|
||||||
|
{i18next.t("general:Default")}
|
||||||
|
</Space>
|
||||||
|
</Option>
|
||||||
|
{
|
||||||
|
this.state.groups?.map((group) => <Option key={group.name} value={`${group.owner}/${group.name}`}>
|
||||||
|
<Space>
|
||||||
|
{group.type === "Physical" ? <UsergroupAddOutlined /> : <HolderOutlined />}
|
||||||
|
{group.displayName}
|
||||||
|
</Space>
|
||||||
|
</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
<Row style={{marginTop: "20px"}}>
|
<Row style={{marginTop: "20px"}}>
|
||||||
<Col style={{lineHeight: "32px", textAlign: "right", paddingRight: "25px"}} span={3}>
|
<Col style={{lineHeight: "32px", textAlign: "right", paddingRight: "25px"}} span={3}>
|
||||||
{Setting.getLabel(i18next.t("ldap:Auto Sync"), i18next.t("ldap:Auto Sync - Tooltip"))} :
|
{Setting.getLabel(i18next.t("ldap:Auto Sync"), i18next.t("ldap:Auto Sync - Tooltip"))} :
|
||||||
|
@ -18,11 +18,7 @@ import * as ModelBackend from "./backend/ModelBackend";
|
|||||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import ModelEditor from "./CasbinEditor";
|
||||||
import {Controlled as CodeMirror} from "react-codemirror2";
|
|
||||||
import "codemirror/lib/codemirror.css";
|
|
||||||
|
|
||||||
require("codemirror/mode/properties/properties");
|
|
||||||
|
|
||||||
const {Option} = Select;
|
const {Option} = Select;
|
||||||
|
|
||||||
@ -147,16 +143,10 @@ class ModelEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("model:Model text"), i18next.t("model:Model text - Tooltip"))} :
|
{Setting.getLabel(i18next.t("model:Model text"), i18next.t("model:Model text - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22}>
|
<Col span={22}>
|
||||||
<div style={{width: "100%"}} >
|
<div style={{position: "relative", height: "500px"}} >
|
||||||
<CodeMirror
|
<ModelEditor
|
||||||
value={this.state.model.modelText}
|
model={this.state.model}
|
||||||
options={{mode: "properties", theme: "default"}}
|
onModelTextChange={(value) => this.updateModelField("modelText", value)}
|
||||||
onBeforeChange={(editor, data, value) => {
|
|
||||||
if (Setting.builtInObject(this.state.model)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.updateModelField("modelText", value);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -72,9 +72,11 @@ class ModelListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -19,6 +19,7 @@ import * as ApplicationBackend from "./backend/ApplicationBackend";
|
|||||||
import * as LdapBackend from "./backend/LdapBackend";
|
import * as LdapBackend from "./backend/LdapBackend";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import * as Conf from "./Conf";
|
import * as Conf from "./Conf";
|
||||||
|
import * as Obfuscator from "./auth/Obfuscator";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import {LinkOutlined} from "@ant-design/icons";
|
import {LinkOutlined} from "@ant-design/icons";
|
||||||
import LdapTable from "./table/LdapTable";
|
import LdapTable from "./table/LdapTable";
|
||||||
@ -112,6 +113,22 @@ class OrganizationEditPage extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updatePasswordObfuscator(key, value) {
|
||||||
|
const organization = this.state.organization;
|
||||||
|
if (organization.passwordObfuscatorType === "") {
|
||||||
|
organization.passwordObfuscatorType = "Plain";
|
||||||
|
}
|
||||||
|
if (key === "type") {
|
||||||
|
organization.passwordObfuscatorType = value;
|
||||||
|
organization.passwordObfuscatorKey = Obfuscator.getRandomKeyForObfuscator(value);
|
||||||
|
} else if (key === "key") {
|
||||||
|
organization.passwordObfuscatorKey = value;
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
organization: organization,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
renderOrganization() {
|
renderOrganization() {
|
||||||
return (
|
return (
|
||||||
<Card size="small" title={
|
<Card size="small" title={
|
||||||
@ -294,6 +311,34 @@ class OrganizationEditPage extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Password obfuscator"), i18next.t("general:Password obfuscator - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: "100%"}}
|
||||||
|
value={this.state.organization.passwordObfuscatorType}
|
||||||
|
onChange={(value => {this.updatePasswordObfuscator("type", value);})}>
|
||||||
|
{
|
||||||
|
[
|
||||||
|
{id: "Plain", name: "Plain"},
|
||||||
|
{id: "AES", name: "AES"},
|
||||||
|
{id: "DES", name: "DES"},
|
||||||
|
].map((obfuscatorType, index) => <Option key={index} value={obfuscatorType.id}>{obfuscatorType.name}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
{
|
||||||
|
(this.state.organization.passwordObfuscatorType === "Plain" || this.state.organization.passwordObfuscatorType === "") ? null : (<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Password obf key"), i18next.t("general:Password obf key - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.organization.passwordObfuscatorKey} onChange={(e) => {this.updatePasswordObfuscator("key", e.target.value);}} />
|
||||||
|
</Col>
|
||||||
|
</Row>)
|
||||||
|
}
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("general:Supported country codes"), i18next.t("general:Supported country codes - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Supported country codes"), i18next.t("general:Supported country codes - Tooltip"))} :
|
||||||
@ -305,6 +350,7 @@ class OrganizationEditPage extends React.Component {
|
|||||||
}}
|
}}
|
||||||
filterOption={(input, option) => (option?.text ?? "").toLowerCase().includes(input.toLowerCase())}
|
filterOption={(input, option) => (option?.text ?? "").toLowerCase().includes(input.toLowerCase())}
|
||||||
>
|
>
|
||||||
|
{Setting.getCountryCodeOption({name: i18next.t("organization:All"), code: "All", phone: 0})}
|
||||||
{
|
{
|
||||||
Setting.getCountryCodeData().map((country) => Setting.getCountryCodeOption(country))
|
Setting.getCountryCodeData().map((country) => Setting.getCountryCodeOption(country))
|
||||||
}
|
}
|
||||||
@ -360,7 +406,7 @@ class OrganizationEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Select virtual={false} style={{width: "100%"}} value={this.state.organization.defaultApplication} onChange={(value => {this.updateOrganizationField("defaultApplication", value);})}
|
<Select virtual={false} style={{width: "100%"}} value={this.state.organization.defaultApplication} onChange={(value => {this.updateOrganizationField("defaultApplication", value);})}
|
||||||
options={this.state.applications?.map((item) => Setting.getOption(item.name, item.name))
|
options={this.state.applications?.map((item) => Setting.getOption(Setting.getApplicationDisplayName(item.name), item.name))
|
||||||
} />
|
} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
@ -446,6 +492,16 @@ class OrganizationEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Enable tour"), i18next.t("general:Enable tour - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={1} >
|
||||||
|
<Switch checked={this.state.organization.enableTour} onChange={checked => {
|
||||||
|
this.updateOrganizationField("enableTour", checked);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("organization:Account items"), i18next.t("organization:Account items - Tooltip"))} :
|
{Setting.getLabel(i18next.t("organization:Account items"), i18next.t("organization:Account items - Tooltip"))} :
|
||||||
@ -518,6 +574,12 @@ class OrganizationEditPage extends React.Component {
|
|||||||
const organization = Setting.deepCopy(this.state.organization);
|
const organization = Setting.deepCopy(this.state.organization);
|
||||||
organization.accountItems = organization.accountItems?.filter(accountItem => accountItem.name !== "Please select an account item");
|
organization.accountItems = organization.accountItems?.filter(accountItem => accountItem.name !== "Please select an account item");
|
||||||
|
|
||||||
|
const passwordObfuscatorErrorMessage = Obfuscator.checkPasswordObfuscator(organization.passwordObfuscatorType, organization.passwordObfuscatorKey);
|
||||||
|
if (passwordObfuscatorErrorMessage.length > 0) {
|
||||||
|
Setting.showMessage("error", passwordObfuscatorErrorMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
OrganizationBackend.updateOrganization(this.state.organization.owner, this.state.organizationName, organization)
|
OrganizationBackend.updateOrganization(this.state.organization.owner, this.state.organizationName, organization)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
|
@ -35,6 +35,8 @@ class OrganizationListPage extends BaseListPage {
|
|||||||
passwordType: "plain",
|
passwordType: "plain",
|
||||||
PasswordSalt: "",
|
PasswordSalt: "",
|
||||||
passwordOptions: [],
|
passwordOptions: [],
|
||||||
|
passwordObfuscatorType: "Plain",
|
||||||
|
passwordObfuscatorKey: "",
|
||||||
countryCodes: ["US"],
|
countryCodes: ["US"],
|
||||||
defaultAvatar: `${Setting.StaticBaseUrl}/img/casbin.svg`,
|
defaultAvatar: `${Setting.StaticBaseUrl}/img/casbin.svg`,
|
||||||
defaultApplication: "",
|
defaultApplication: "",
|
||||||
@ -44,6 +46,7 @@ class OrganizationListPage extends BaseListPage {
|
|||||||
defaultPassword: "",
|
defaultPassword: "",
|
||||||
enableSoftDeletion: false,
|
enableSoftDeletion: false,
|
||||||
isProfilePublic: true,
|
isProfilePublic: true,
|
||||||
|
enableTour: true,
|
||||||
accountItems: [
|
accountItems: [
|
||||||
{name: "Organization", visible: true, viewRule: "Public", modifyRule: "Admin"},
|
{name: "Organization", visible: true, viewRule: "Public", modifyRule: "Admin"},
|
||||||
{name: "ID", visible: true, viewRule: "Public", modifyRule: "Immutable"},
|
{name: "ID", visible: true, viewRule: "Public", modifyRule: "Immutable"},
|
||||||
@ -87,6 +90,7 @@ class OrganizationListPage extends BaseListPage {
|
|||||||
{Name: "Multi-factor authentication", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
{Name: "Multi-factor authentication", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||||
{Name: "WebAuthn credentials", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
{Name: "WebAuthn credentials", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||||
{Name: "Managed accounts", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
{Name: "Managed accounts", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||||
|
{Name: "MFA accounts", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -113,11 +117,11 @@ class OrganizationListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
|
||||||
pagination: {
|
pagination: {
|
||||||
...this.state.pagination,
|
...this.state.pagination,
|
||||||
total: this.state.pagination.total - 1},
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
window.dispatchEvent(new Event("storageOrganizationsChanged"));
|
window.dispatchEvent(new Event("storageOrganizationsChanged"));
|
||||||
} else {
|
} else {
|
||||||
|
@ -70,9 +70,11 @@ class PaymentListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -122,7 +122,7 @@ class PaymentResultPage extends React.Component {
|
|||||||
payment: payment,
|
payment: payment,
|
||||||
});
|
});
|
||||||
if (payment.state === "Created") {
|
if (payment.state === "Created") {
|
||||||
if (["PayPal", "Stripe", "Alipay", "WeChat Pay"].includes(payment.type)) {
|
if (["PayPal", "Stripe", "Alipay", "WeChat Pay", "Balance"].includes(payment.type)) {
|
||||||
this.setState({
|
this.setState({
|
||||||
timeout: setTimeout(async() => {
|
timeout: setTimeout(async() => {
|
||||||
await PaymentBackend.notifyPayment(this.state.owner, this.state.paymentName);
|
await PaymentBackend.notifyPayment(this.state.owner, this.state.paymentName);
|
||||||
|
@ -487,6 +487,7 @@ class PermissionEditPage extends React.Component {
|
|||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully saved"));
|
Setting.showMessage("success", i18next.t("general:Successfully saved"));
|
||||||
this.setState({
|
this.setState({
|
||||||
|
organizationName: this.state.permission.owner,
|
||||||
permissionName: this.state.permission.name,
|
permissionName: this.state.permission.name,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -69,9 +69,11 @@ class PermissionListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -63,9 +63,11 @@ class PlanListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -59,9 +59,11 @@ class PricingListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -65,9 +65,11 @@ class ProductListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -725,7 +725,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
(this.state.provider.category === "Web3") ||
|
(this.state.provider.category === "Web3") ||
|
||||||
(this.state.provider.category === "Storage" && this.state.provider.type === "Local File System") ||
|
(this.state.provider.category === "Storage" && this.state.provider.type === "Local File System") ||
|
||||||
(this.state.provider.category === "SMS" && this.state.provider.type === "Custom HTTP SMS") ||
|
(this.state.provider.category === "SMS" && this.state.provider.type === "Custom HTTP SMS") ||
|
||||||
(this.state.provider.category === "Notification" && (this.state.provider.type === "Google Chat" || this.state.provider.type === "Custom HTTP")) ? null : (
|
(this.state.provider.category === "Notification" && (this.state.provider.type === "Google Chat" || this.state.provider.type === "Custom HTTP") || this.state.provider.type === "Balance") ? null : (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{
|
{
|
||||||
(this.state.provider.category === "Storage" && this.state.provider.type === "Google Cloud Storage") ||
|
(this.state.provider.category === "Storage" && this.state.provider.type === "Google Cloud Storage") ||
|
||||||
@ -843,7 +843,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
this.state.provider.type !== "ADFS" && this.state.provider.type !== "AzureAD" && this.state.provider.type !== "AzureADB2C" && this.state.provider.type !== "Casdoor" && this.state.provider.type !== "Okta" ? null : (
|
this.state.provider.type !== "ADFS" && this.state.provider.type !== "AzureAD" && this.state.provider.type !== "AzureADB2C" && (this.state.provider.type !== "Casdoor" && this.state.category !== "Storage") && this.state.provider.type !== "Okta" ? null : (
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={2}>
|
<Col style={{marginTop: "5px"}} span={2}>
|
||||||
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
|
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
|
||||||
@ -870,7 +870,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
)}
|
)}
|
||||||
{["Custom HTTP SMS", "Local File System", "MinIO", "Tencent Cloud COS", "Google Cloud Storage", "Qiniu Cloud Kodo", "Synology"].includes(this.state.provider.type) ? null : (
|
{["Custom HTTP SMS", "Local File System", "MinIO", "Tencent Cloud COS", "Google Cloud Storage", "Qiniu Cloud Kodo", "Synology", "Casdoor"].includes(this.state.provider.type) ? null : (
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={2}>
|
<Col style={{marginTop: "5px"}} span={2}>
|
||||||
{Setting.getLabel(i18next.t("provider:Endpoint (Intranet)"), i18next.t("provider:Region endpoint for Intranet"))} :
|
{Setting.getLabel(i18next.t("provider:Endpoint (Intranet)"), i18next.t("provider:Region endpoint for Intranet"))} :
|
||||||
@ -885,7 +885,9 @@ class ProviderEditPage extends React.Component {
|
|||||||
{["Custom HTTP SMS", "Local File System"].includes(this.state.provider.type) ? null : (
|
{["Custom HTTP SMS", "Local File System"].includes(this.state.provider.type) ? null : (
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={2}>
|
<Col style={{marginTop: "5px"}} span={2}>
|
||||||
{Setting.getLabel(i18next.t("provider:Bucket"), i18next.t("provider:Bucket - Tooltip"))} :
|
{["Casdoor"].includes(this.state.provider.type) ?
|
||||||
|
Setting.getLabel(i18next.t("general:Provider"), i18next.t("provider:Provider - Tooltip"))
|
||||||
|
: Setting.getLabel(i18next.t("provider:Bucket"), i18next.t("provider:Bucket - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Input value={this.state.provider.bucket} onChange={e => {
|
<Input value={this.state.provider.bucket} onChange={e => {
|
||||||
@ -906,7 +908,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
)}
|
)}
|
||||||
{["Custom HTTP SMS", "Google Cloud Storage", "Qiniu Cloud Kodo", "Synology"].includes(this.state.provider.type) ? null : (
|
{["Custom HTTP SMS", "Qiniu Cloud Kodo", "Synology", "Casdoor"].includes(this.state.provider.type) ? null : (
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={2}>
|
<Col style={{marginTop: "5px"}} span={2}>
|
||||||
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
|
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
|
||||||
@ -918,10 +920,24 @@ class ProviderEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
)}
|
)}
|
||||||
{["AWS S3", "Tencent Cloud COS", "Qiniu Cloud Kodo"].includes(this.state.provider.type) ? (
|
{["Casdoor"].includes(this.state.provider.type) ? (
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={2}>
|
<Col style={{marginTop: "5px"}} span={2}>
|
||||||
{Setting.getLabel(i18next.t("provider:Region ID"), i18next.t("provider:Region ID - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.provider.content} onChange={e => {
|
||||||
|
this.updateProviderField("content", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
) : null}
|
||||||
|
{["AWS S3", "Tencent Cloud COS", "Qiniu Cloud Kodo", "Casdoor"].includes(this.state.provider.type) ? (
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={2}>
|
||||||
|
{["Casdoor"].includes(this.state.provider.type) ?
|
||||||
|
Setting.getLabel(i18next.t("general:Application"), i18next.t("general:Application - Tooltip")) :
|
||||||
|
Setting.getLabel(i18next.t("provider:Region ID"), i18next.t("provider:Region ID - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Input value={this.state.provider.regionId} onChange={e => {
|
<Input value={this.state.provider.regionId} onChange={e => {
|
||||||
@ -1298,7 +1314,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
(this.state.provider.type === "Alipay" || this.state.provider.type === "WeChat Pay") ? (
|
(this.state.provider.type === "Alipay" || this.state.provider.type === "WeChat Pay" || this.state.provider.type === "Casdoor") ? (
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("general:Cert"), i18next.t("general:Cert - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Cert"), i18next.t("general:Cert - Tooltip"))} :
|
||||||
|
@ -76,9 +76,11 @@ class ProviderListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -40,9 +40,11 @@ class ResourceListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -61,9 +61,11 @@ class RoleListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -27,9 +27,11 @@ class SessionListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -56,6 +56,8 @@ export const Countries = [
|
|||||||
{label: "Українська", key: "uk", country: "UA", alt: "Українська"},
|
{label: "Українська", key: "uk", country: "UA", alt: "Українська"},
|
||||||
{label: "Қазақ", key: "kk", country: "KZ", alt: "Қазақ"},
|
{label: "Қазақ", key: "kk", country: "KZ", alt: "Қазақ"},
|
||||||
{label: "فارسی", key: "fa", country: "IR", alt: "فارسی"},
|
{label: "فارسی", key: "fa", country: "IR", alt: "فارسی"},
|
||||||
|
{label: "Čeština", key: "cs", country: "CZ", alt: "Čeština"},
|
||||||
|
{label: "Slovenčina", key: "sk", country: "SK", alt: "Slovenčina"},
|
||||||
];
|
];
|
||||||
|
|
||||||
export function getThemeData(organization, application) {
|
export function getThemeData(organization, application) {
|
||||||
@ -227,6 +229,10 @@ export const OtherProviderInfo = {
|
|||||||
logo: `${StaticBaseUrl}/img/social_synology.png`,
|
logo: `${StaticBaseUrl}/img/social_synology.png`,
|
||||||
url: "https://www.synology.com/en-global/dsm/feature/file_sharing",
|
url: "https://www.synology.com/en-global/dsm/feature/file_sharing",
|
||||||
},
|
},
|
||||||
|
"Casdoor": {
|
||||||
|
logo: `${StaticBaseUrl}/img/casdoor.png`,
|
||||||
|
url: "https://casdoor.org/docs/provider/storage/overview",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
SAML: {
|
SAML: {
|
||||||
"Aliyun IDaaS": {
|
"Aliyun IDaaS": {
|
||||||
@ -247,6 +253,10 @@ export const OtherProviderInfo = {
|
|||||||
logo: `${StaticBaseUrl}/img/payment_paypal.png`,
|
logo: `${StaticBaseUrl}/img/payment_paypal.png`,
|
||||||
url: "",
|
url: "",
|
||||||
},
|
},
|
||||||
|
"Balance": {
|
||||||
|
logo: `${StaticBaseUrl}/img/payment_balance.svg`,
|
||||||
|
url: "",
|
||||||
|
},
|
||||||
"Alipay": {
|
"Alipay": {
|
||||||
logo: `${StaticBaseUrl}/img/payment_alipay.png`,
|
logo: `${StaticBaseUrl}/img/payment_alipay.png`,
|
||||||
url: "https://www.alipay.com/",
|
url: "https://www.alipay.com/",
|
||||||
@ -277,6 +287,14 @@ export const OtherProviderInfo = {
|
|||||||
logo: `${StaticBaseUrl}/img/social_recaptcha.png`,
|
logo: `${StaticBaseUrl}/img/social_recaptcha.png`,
|
||||||
url: "https://www.google.com/recaptcha",
|
url: "https://www.google.com/recaptcha",
|
||||||
},
|
},
|
||||||
|
"reCAPTCHA v2": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_recaptcha.png`,
|
||||||
|
url: "https://www.google.com/recaptcha",
|
||||||
|
},
|
||||||
|
"reCAPTCHA v3": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_recaptcha.png`,
|
||||||
|
url: "https://www.google.com/recaptcha",
|
||||||
|
},
|
||||||
"hCaptcha": {
|
"hCaptcha": {
|
||||||
logo: `${StaticBaseUrl}/img/social_hcaptcha.png`,
|
logo: `${StaticBaseUrl}/img/social_hcaptcha.png`,
|
||||||
url: "https://www.hcaptcha.com",
|
url: "https://www.hcaptcha.com",
|
||||||
@ -400,6 +418,9 @@ export function getCountryCode(country) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getCountryCodeData(countryCodes = phoneNumber.getCountries()) {
|
export function getCountryCodeData(countryCodes = phoneNumber.getCountries()) {
|
||||||
|
if (countryCodes?.includes("All")) {
|
||||||
|
countryCodes = phoneNumber.getCountries();
|
||||||
|
}
|
||||||
return countryCodes?.map((countryCode) => {
|
return countryCodes?.map((countryCode) => {
|
||||||
if (phoneNumber.isSupportedCountry(countryCode)) {
|
if (phoneNumber.isSupportedCountry(countryCode)) {
|
||||||
const name = initCountries().getName(countryCode, getLanguage());
|
const name = initCountries().getName(countryCode, getLanguage());
|
||||||
@ -418,10 +439,10 @@ export function getCountryCodeOption(country) {
|
|||||||
<Option key={country.code} value={country.code} label={`+${country.phone}`} text={`${country.name}, ${country.code}, ${country.phone}`} >
|
<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 style={{display: "flex", justifyContent: "space-between", marginRight: "10px"}}>
|
||||||
<div>
|
<div>
|
||||||
{getCountryImage(country)}
|
{country.code === "All" ? null : getCountryImage(country)}
|
||||||
{`${country.name}`}
|
{`${country.name}`}
|
||||||
</div>
|
</div>
|
||||||
{`+${country.phone}`}
|
{country.code === "All" ? null : `+${country.phone}`}
|
||||||
</div>
|
</div>
|
||||||
</Option>
|
</Option>
|
||||||
);
|
);
|
||||||
@ -1056,6 +1077,7 @@ export function getProviderTypeOptions(category) {
|
|||||||
{id: "Qiniu Cloud Kodo", name: "Qiniu Cloud Kodo"},
|
{id: "Qiniu Cloud Kodo", name: "Qiniu Cloud Kodo"},
|
||||||
{id: "Google Cloud Storage", name: "Google Cloud Storage"},
|
{id: "Google Cloud Storage", name: "Google Cloud Storage"},
|
||||||
{id: "Synology", name: "Synology"},
|
{id: "Synology", name: "Synology"},
|
||||||
|
{id: "Casdoor", name: "Casdoor"},
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
} else if (category === "SAML") {
|
} else if (category === "SAML") {
|
||||||
@ -1067,6 +1089,7 @@ export function getProviderTypeOptions(category) {
|
|||||||
} else if (category === "Payment") {
|
} else if (category === "Payment") {
|
||||||
return ([
|
return ([
|
||||||
{id: "Dummy", name: "Dummy"},
|
{id: "Dummy", name: "Dummy"},
|
||||||
|
{id: "Balance", name: "Balance"},
|
||||||
{id: "Alipay", name: "Alipay"},
|
{id: "Alipay", name: "Alipay"},
|
||||||
{id: "WeChat Pay", name: "WeChat Pay"},
|
{id: "WeChat Pay", name: "WeChat Pay"},
|
||||||
{id: "PayPal", name: "PayPal"},
|
{id: "PayPal", name: "PayPal"},
|
||||||
@ -1076,7 +1099,8 @@ export function getProviderTypeOptions(category) {
|
|||||||
} else if (category === "Captcha") {
|
} else if (category === "Captcha") {
|
||||||
return ([
|
return ([
|
||||||
{id: "Default", name: "Default"},
|
{id: "Default", name: "Default"},
|
||||||
{id: "reCAPTCHA", name: "reCAPTCHA"},
|
{id: "reCAPTCHA v2", name: "reCAPTCHA v2"},
|
||||||
|
{id: "reCAPTCHA v3", name: "reCAPTCHA v3"},
|
||||||
{id: "hCaptcha", name: "hCaptcha"},
|
{id: "hCaptcha", name: "hCaptcha"},
|
||||||
{id: "Aliyun Captcha", name: "Aliyun Captcha"},
|
{id: "Aliyun Captcha", name: "Aliyun Captcha"},
|
||||||
{id: "GEETEST", name: "GEETEST"},
|
{id: "GEETEST", name: "GEETEST"},
|
||||||
@ -1147,7 +1171,7 @@ export function renderLogo(application) {
|
|||||||
|
|
||||||
function isSigninMethodEnabled(application, signinMethod) {
|
function isSigninMethodEnabled(application, signinMethod) {
|
||||||
if (application && application.signinMethods) {
|
if (application && application.signinMethods) {
|
||||||
return application.signinMethods.filter(item => item.name === signinMethod).length > 0;
|
return application.signinMethods.filter(item => item.name === signinMethod && item.rule !== "Hide-Password").length > 0;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -1364,6 +1388,13 @@ export function getApplicationName(application) {
|
|||||||
return `${application?.owner}/${application?.name}`;
|
return `${application?.owner}/${application?.name}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getApplicationDisplayName(application) {
|
||||||
|
if (application.isShared) {
|
||||||
|
return `${application.name}(Shared)`;
|
||||||
|
}
|
||||||
|
return application.name;
|
||||||
|
}
|
||||||
|
|
||||||
export function getRandomName() {
|
export function getRandomName() {
|
||||||
return Math.random().toString(36).slice(-6);
|
return Math.random().toString(36).slice(-6);
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user