mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-08 09:01:00 +08:00
Compare commits
29 Commits
Author | SHA1 | Date | |
---|---|---|---|
9bbe5afb7c | |||
b42391c6ce | |||
fb035a5353 | |||
b1f68a60a4 | |||
201d704a31 | |||
bf91ad6c97 | |||
3ccc0339c7 | |||
1f2b0a3587 | |||
0b3feb0d5f | |||
568c0e2c3d | |||
f4ad2b4034 | |||
c9f8727890 | |||
e2e3c1fbb8 | |||
73915ac0a0 | |||
bf9d55ff40 | |||
b36fb50239 | |||
4307baa759 | |||
3964bae1df | |||
d9b97d70be | |||
ca224fdd4c | |||
37daea2bbc | |||
af231bf946 | |||
6dc7b4d533 | |||
12cc0f429e | |||
8cc22dec91 | |||
0c08ae5365 | |||
c3485268d3 | |||
64a4956c42 | |||
855bdf47e8 |
8
.gitattributes
vendored
8
.gitattributes
vendored
@ -1,5 +1,5 @@
|
|||||||
*.go linguist-detectable=true
|
*.go linguist-detectable=true
|
||||||
*.js linguist-detectable=false
|
*.js linguist-detectable=false
|
||||||
# Declare files that will always have LF line endings on checkout.
|
# Declare files that will always have LF line endings on checkout.
|
||||||
# Git will always convert line endings to LF on checkout. You should use this for files that must keep LF endings, even on Windows.
|
# Git will always convert line endings to LF on checkout. You should use this for files that must keep LF endings, even on Windows.
|
||||||
*.sh text eol=lf
|
*.sh text eol=lf
|
204
README.md
204
README.md
@ -1,102 +1,102 @@
|
|||||||
<h1 align="center" style="border-bottom: none;">📦⚡️ Casdoor</h1>
|
<h1 align="center" style="border-bottom: none;">📦⚡️ Casdoor</h1>
|
||||||
<h3 align="center">An open-source UI-first Identity and Access Management (IAM) / Single-Sign-On (SSO) platform with web UI supporting OAuth 2.0, OIDC, SAML, CAS, LDAP, SCIM, WebAuthn, TOTP, MFA and RADIUS</h3>
|
<h3 align="center">An open-source UI-first Identity and Access Management (IAM) / Single-Sign-On (SSO) platform with web UI supporting OAuth 2.0, OIDC, SAML, CAS, LDAP, SCIM, WebAuthn, TOTP, MFA and RADIUS</h3>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="#badge">
|
<a href="#badge">
|
||||||
<img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">
|
<img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://hub.docker.com/r/casbin/casdoor">
|
<a href="https://hub.docker.com/r/casbin/casdoor">
|
||||||
<img alt="docker pull casbin/casdoor" src="https://img.shields.io/docker/pulls/casbin/casdoor.svg">
|
<img alt="docker pull casbin/casdoor" src="https://img.shields.io/docker/pulls/casbin/casdoor.svg">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/casdoor/casdoor/actions/workflows/build.yml">
|
<a href="https://github.com/casdoor/casdoor/actions/workflows/build.yml">
|
||||||
<img alt="GitHub Workflow Status (branch)" src="https://github.com/casdoor/casdoor/workflows/Build/badge.svg?style=flat-square">
|
<img alt="GitHub Workflow Status (branch)" src="https://github.com/casdoor/casdoor/workflows/Build/badge.svg?style=flat-square">
|
||||||
</a>
|
</a>
|
||||||
<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/r/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>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://goreportcard.com/report/github.com/casdoor/casdoor">
|
<a href="https://goreportcard.com/report/github.com/casdoor/casdoor">
|
||||||
<img alt="Go Report Card" src="https://goreportcard.com/badge/github.com/casdoor/casdoor?style=flat-square">
|
<img alt="Go Report Card" src="https://goreportcard.com/badge/github.com/casdoor/casdoor?style=flat-square">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/casdoor/casdoor/blob/master/LICENSE">
|
<a href="https://github.com/casdoor/casdoor/blob/master/LICENSE">
|
||||||
<img src="https://img.shields.io/github/license/casdoor/casdoor?style=flat-square" alt="license">
|
<img src="https://img.shields.io/github/license/casdoor/casdoor?style=flat-square" alt="license">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/casdoor/casdoor/issues">
|
<a href="https://github.com/casdoor/casdoor/issues">
|
||||||
<img alt="GitHub issues" src="https://img.shields.io/github/issues/casdoor/casdoor?style=flat-square">
|
<img alt="GitHub issues" src="https://img.shields.io/github/issues/casdoor/casdoor?style=flat-square">
|
||||||
</a>
|
</a>
|
||||||
<a href="#">
|
<a href="#">
|
||||||
<img alt="GitHub stars" src="https://img.shields.io/github/stars/casdoor/casdoor?style=flat-square">
|
<img alt="GitHub stars" src="https://img.shields.io/github/stars/casdoor/casdoor?style=flat-square">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/casdoor/casdoor/network">
|
<a href="https://github.com/casdoor/casdoor/network">
|
||||||
<img alt="GitHub forks" src="https://img.shields.io/github/forks/casdoor/casdoor?style=flat-square">
|
<img alt="GitHub forks" src="https://img.shields.io/github/forks/casdoor/casdoor?style=flat-square">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://crowdin.com/project/casdoor-site">
|
<a href="https://crowdin.com/project/casdoor-site">
|
||||||
<img alt="Crowdin" src="https://badges.crowdin.net/casdoor-site/localized.svg">
|
<img alt="Crowdin" src="https://badges.crowdin.net/casdoor-site/localized.svg">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://discord.gg/5rPsrAzK7S">
|
<a href="https://discord.gg/5rPsrAzK7S">
|
||||||
<img alt="Discord" src="https://img.shields.io/discord/1022748306096537660?style=flat-square&logo=discord&label=discord&color=5865F2">
|
<img alt="Discord" src="https://img.shields.io/discord/1022748306096537660?style=flat-square&logo=discord&label=discord&color=5865F2">
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<sup>Sponsored by</sup>
|
<sup>Sponsored by</sup>
|
||||||
<br>
|
<br>
|
||||||
<a href="https://stytch.com/docs?utm_source=oss-sponsorship&utm_medium=paid_sponsorship&utm_campaign=casbin">
|
<a href="https://stytch.com/docs?utm_source=oss-sponsorship&utm_medium=paid_sponsorship&utm_campaign=casbin">
|
||||||
<picture>
|
<picture>
|
||||||
<source media="(prefers-color-scheme: dark)" srcset="https://cdn.casbin.org/img/stytch-white.png">
|
<source media="(prefers-color-scheme: dark)" srcset="https://cdn.casbin.org/img/stytch-white.png">
|
||||||
<source media="(prefers-color-scheme: light)" srcset="https://cdn.casbin.org/img/stytch-charcoal.png">
|
<source media="(prefers-color-scheme: light)" srcset="https://cdn.casbin.org/img/stytch-charcoal.png">
|
||||||
<img src="https://cdn.casbin.org/img/stytch-charcoal.png" width="275">
|
<img src="https://cdn.casbin.org/img/stytch-charcoal.png" width="275">
|
||||||
</picture>
|
</picture>
|
||||||
</a><br/>
|
</a><br/>
|
||||||
<a href="https://stytch.com/docs?utm_source=oss-sponsorship&utm_medium=paid_sponsorship&utm_campaign=casbin"><b>Build auth with fraud prevention, faster.</b><br/> Try Stytch for API-first authentication, user & org management, multi-tenant SSO, MFA, device fingerprinting, and more.</a>
|
<a href="https://stytch.com/docs?utm_source=oss-sponsorship&utm_medium=paid_sponsorship&utm_campaign=casbin"><b>Build auth with fraud prevention, faster.</b><br/> Try Stytch for API-first authentication, user & org management, multi-tenant SSO, MFA, device fingerprinting, and more.</a>
|
||||||
<br>
|
<br>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## Online demo
|
## Online demo
|
||||||
|
|
||||||
- Read-only site: https://door.casdoor.com (any modification operation will fail)
|
- Read-only site: https://door.casdoor.com (any modification operation will fail)
|
||||||
- Writable site: https://demo.casdoor.com (original data will be restored for every 5 minutes)
|
- Writable site: https://demo.casdoor.com (original data will be restored for every 5 minutes)
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
https://casdoor.org
|
https://casdoor.org
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
- By source code: https://casdoor.org/docs/basic/server-installation
|
- By source code: https://casdoor.org/docs/basic/server-installation
|
||||||
- By Docker: https://casdoor.org/docs/basic/try-with-docker
|
- By Docker: https://casdoor.org/docs/basic/try-with-docker
|
||||||
- By Kubernetes Helm: https://casdoor.org/docs/basic/try-with-helm
|
- By Kubernetes Helm: https://casdoor.org/docs/basic/try-with-helm
|
||||||
|
|
||||||
## How to connect to Casdoor?
|
## How to connect to Casdoor?
|
||||||
|
|
||||||
https://casdoor.org/docs/how-to-connect/overview
|
https://casdoor.org/docs/how-to-connect/overview
|
||||||
|
|
||||||
## Casdoor Public API
|
## Casdoor Public API
|
||||||
|
|
||||||
- Docs: https://casdoor.org/docs/basic/public-api
|
- Docs: https://casdoor.org/docs/basic/public-api
|
||||||
- Swagger: https://door.casdoor.com/swagger
|
- Swagger: https://door.casdoor.com/swagger
|
||||||
|
|
||||||
## Integrations
|
## Integrations
|
||||||
|
|
||||||
https://casdoor.org/docs/category/integrations
|
https://casdoor.org/docs/category/integrations
|
||||||
|
|
||||||
## How to contact?
|
## How to contact?
|
||||||
|
|
||||||
- Discord: https://discord.gg/5rPsrAzK7S
|
- Discord: https://discord.gg/5rPsrAzK7S
|
||||||
- Contact: https://casdoor.org/help
|
- Contact: https://casdoor.org/help
|
||||||
|
|
||||||
## Contribute
|
## Contribute
|
||||||
|
|
||||||
For casdoor, if you have any questions, you can give Issues, or you can also directly start Pull Requests(but we recommend giving issues first to communicate with the community).
|
For casdoor, if you have any questions, you can give Issues, or you can also directly start Pull Requests(but we recommend giving issues first to communicate with the community).
|
||||||
|
|
||||||
### I18n translation
|
### I18n translation
|
||||||
|
|
||||||
If you are contributing to casdoor, please note that we use [Crowdin](https://crowdin.com/project/casdoor-site) as translating platform and i18next as translating tool. When you add some words using i18next in the `web/` directory, please remember to add what you have added to the `web/src/locales/en/data.json` file.
|
If you are contributing to casdoor, please note that we use [Crowdin](https://crowdin.com/project/casdoor-site) as translating platform and i18next as translating tool. When you add some words using i18next in the `web/` directory, please remember to add what you have added to the `web/src/locales/en/data.json` file.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
[Apache-2.0](https://github.com/casdoor/casdoor/blob/master/LICENSE)
|
[Apache-2.0](https://github.com/casdoor/casdoor/blob/master/LICENSE)
|
||||||
|
@ -15,32 +15,51 @@
|
|||||||
package captcha
|
package captcha
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||||
"errors"
|
openapiutil "github.com/alibabacloud-go/openapi-util/service"
|
||||||
"fmt"
|
teaUtil "github.com/alibabacloud-go/tea-utils/v2/service"
|
||||||
"io"
|
"github.com/alibabacloud-go/tea/tea"
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const AliyunCaptchaVerifyUrl = "http://afs.aliyuncs.com"
|
const AliyunCaptchaVerifyUrl = "captcha.cn-shanghai.aliyuncs.com"
|
||||||
|
|
||||||
type captchaSuccessResponse struct {
|
type VerifyCaptchaRequest struct {
|
||||||
Code int `json:"Code"`
|
CaptchaVerifyParam *string `json:"CaptchaVerifyParam,omitempty" xml:"CaptchaVerifyParam,omitempty"`
|
||||||
Msg string `json:"Msg"`
|
SceneId *string `json:"SceneId,omitempty" xml:"SceneId,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type captchaFailResponse struct {
|
type VerifyCaptchaResponseBodyResult struct {
|
||||||
Code string `json:"Code"`
|
VerifyResult *bool `json:"VerifyResult,omitempty" xml:"VerifyResult,omitempty"`
|
||||||
Message string `json:"Message"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type VerifyCaptchaResponseBody struct {
|
||||||
|
Code *string `json:"Code,omitempty" xml:"Code,omitempty"`
|
||||||
|
Message *string `json:"Message,omitempty" xml:"Message,omitempty"`
|
||||||
|
// Id of the request
|
||||||
|
RequestId *string `json:"RequestId,omitempty" xml:"RequestId,omitempty"`
|
||||||
|
Result *VerifyCaptchaResponseBodyResult `json:"Result,omitempty" xml:"Result,omitempty" type:"Struct"`
|
||||||
|
Success *bool `json:"Success,omitempty" xml:"Success,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type VerifyIntelligentCaptchaResponseBodyResult struct {
|
||||||
|
VerifyCode *string `json:"VerifyCode,omitempty" xml:"VerifyCode,omitempty"`
|
||||||
|
VerifyResult *bool `json:"VerifyResult,omitempty" xml:"VerifyResult,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type VerifyIntelligentCaptchaResponseBody struct {
|
||||||
|
Code *string `json:"Code,omitempty" xml:"Code,omitempty"`
|
||||||
|
Message *string `json:"Message,omitempty" xml:"Message,omitempty"`
|
||||||
|
// Id of the request
|
||||||
|
RequestId *string `json:"RequestId,omitempty" xml:"RequestId,omitempty"`
|
||||||
|
Result *VerifyIntelligentCaptchaResponseBodyResult `json:"Result,omitempty" xml:"Result,omitempty" type:"Struct"`
|
||||||
|
Success *bool `json:"Success,omitempty" xml:"Success,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type VerifyIntelligentCaptchaResponse struct {
|
||||||
|
Headers map[string]*string `json:"headers,omitempty" xml:"headers,omitempty" require:"true"`
|
||||||
|
StatusCode *int32 `json:"statusCode,omitempty" xml:"statusCode,omitempty" require:"true"`
|
||||||
|
Body *VerifyIntelligentCaptchaResponseBody `json:"body,omitempty" xml:"body,omitempty" require:"true"`
|
||||||
|
}
|
||||||
type AliyunCaptchaProvider struct{}
|
type AliyunCaptchaProvider struct{}
|
||||||
|
|
||||||
func NewAliyunCaptchaProvider() *AliyunCaptchaProvider {
|
func NewAliyunCaptchaProvider() *AliyunCaptchaProvider {
|
||||||
@ -48,68 +67,69 @@ func NewAliyunCaptchaProvider() *AliyunCaptchaProvider {
|
|||||||
return captcha
|
return captcha
|
||||||
}
|
}
|
||||||
|
|
||||||
func contentEscape(str string) string {
|
func (captcha *AliyunCaptchaProvider) VerifyCaptcha(token, clientId, clientSecret, clientId2 string) (bool, error) {
|
||||||
str = strings.Replace(str, " ", "%20", -1)
|
config := &openapi.Config{}
|
||||||
str = url.QueryEscape(str)
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
|
|
||||||
func (captcha *AliyunCaptchaProvider) VerifyCaptcha(token, clientSecret string) (bool, error) {
|
config.Endpoint = tea.String(AliyunCaptchaVerifyUrl)
|
||||||
pathData, err := url.ParseQuery(token)
|
config.ConnectTimeout = tea.Int(5000)
|
||||||
|
config.ReadTimeout = tea.Int(5000)
|
||||||
|
config.AccessKeyId = tea.String(clientId)
|
||||||
|
config.AccessKeySecret = tea.String(clientSecret)
|
||||||
|
|
||||||
|
client := new(openapi.Client)
|
||||||
|
err := client.Init(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
pathData["Action"] = []string{"AuthenticateSig"}
|
request := VerifyCaptchaRequest{CaptchaVerifyParam: tea.String(token), SceneId: tea.String(clientId2)}
|
||||||
pathData["Format"] = []string{"json"}
|
|
||||||
pathData["SignatureMethod"] = []string{"HMAC-SHA1"}
|
|
||||||
pathData["SignatureNonce"] = []string{strconv.FormatInt(time.Now().UnixNano(), 10)}
|
|
||||||
pathData["SignatureVersion"] = []string{"1.0"}
|
|
||||||
pathData["Timestamp"] = []string{time.Now().UTC().Format("2006-01-02T15:04:05Z")}
|
|
||||||
pathData["Version"] = []string{"2018-01-12"}
|
|
||||||
|
|
||||||
var keys []string
|
err = teaUtil.ValidateModel(&request)
|
||||||
for k := range pathData {
|
|
||||||
keys = append(keys, k)
|
|
||||||
}
|
|
||||||
sort.Strings(keys)
|
|
||||||
|
|
||||||
sortQuery := ""
|
|
||||||
for _, k := range keys {
|
|
||||||
sortQuery += k + "=" + contentEscape(pathData[k][0]) + "&"
|
|
||||||
}
|
|
||||||
sortQuery = strings.TrimSuffix(sortQuery, "&")
|
|
||||||
|
|
||||||
stringToSign := fmt.Sprintf("GET&%s&%s", url.QueryEscape("/"), url.QueryEscape(sortQuery))
|
|
||||||
|
|
||||||
signature := util.GetHmacSha1(clientSecret+"&", stringToSign)
|
|
||||||
|
|
||||||
resp, err := http.Get(fmt.Sprintf("%s?%s&Signature=%s", AliyunCaptchaVerifyUrl, sortQuery, url.QueryEscape(signature)))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
runtime := &teaUtil.RuntimeOptions{}
|
||||||
body, err := io.ReadAll(resp.Body)
|
|
||||||
|
body := map[string]interface{}{}
|
||||||
|
if !tea.BoolValue(teaUtil.IsUnset(request.CaptchaVerifyParam)) {
|
||||||
|
body["CaptchaVerifyParam"] = request.CaptchaVerifyParam
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tea.BoolValue(teaUtil.IsUnset(request.SceneId)) {
|
||||||
|
body["SceneId"] = request.SceneId
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &openapi.OpenApiRequest{
|
||||||
|
Body: openapiutil.ParseToMap(body),
|
||||||
|
}
|
||||||
|
params := &openapi.Params{
|
||||||
|
Action: tea.String("VerifyIntelligentCaptcha"),
|
||||||
|
Version: tea.String("2023-03-05"),
|
||||||
|
Protocol: tea.String("HTTPS"),
|
||||||
|
Pathname: tea.String("/"),
|
||||||
|
Method: tea.String("POST"),
|
||||||
|
AuthType: tea.String("AK"),
|
||||||
|
Style: tea.String("RPC"),
|
||||||
|
ReqBodyType: tea.String("formData"),
|
||||||
|
BodyType: tea.String("json"),
|
||||||
|
}
|
||||||
|
|
||||||
|
res := &VerifyIntelligentCaptchaResponse{}
|
||||||
|
|
||||||
|
resBody, err := client.CallApi(params, req, runtime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return handleCaptchaResponse(body)
|
err = tea.Convert(resBody, &res)
|
||||||
}
|
|
||||||
|
|
||||||
func handleCaptchaResponse(body []byte) (bool, error) {
|
|
||||||
captchaResp := &captchaSuccessResponse{}
|
|
||||||
err := json.Unmarshal(body, captchaResp)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
captchaFailResp := &captchaFailResponse{}
|
return false, err
|
||||||
err = json.Unmarshal(body, captchaFailResp)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, errors.New(captchaFailResp.Message)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, nil
|
if res.Body.Result.VerifyResult != nil && *res.Body.Result.VerifyResult {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,6 @@ func NewDefaultCaptchaProvider() *DefaultCaptchaProvider {
|
|||||||
return captcha
|
return captcha
|
||||||
}
|
}
|
||||||
|
|
||||||
func (captcha *DefaultCaptchaProvider) VerifyCaptcha(token, clientSecret string) (bool, error) {
|
func (captcha *DefaultCaptchaProvider) VerifyCaptcha(token, clientId, clientSecret, clientId2 string) (bool, error) {
|
||||||
return object.VerifyCaptcha(clientSecret, token), nil
|
return object.VerifyCaptcha(clientSecret, token), nil
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ func NewGEETESTCaptchaProvider() *GEETESTCaptchaProvider {
|
|||||||
return captcha
|
return captcha
|
||||||
}
|
}
|
||||||
|
|
||||||
func (captcha *GEETESTCaptchaProvider) VerifyCaptcha(token, clientSecret string) (bool, error) {
|
func (captcha *GEETESTCaptchaProvider) VerifyCaptcha(token, clientId, clientSecret, clientId2 string) (bool, error) {
|
||||||
pathData, err := url.ParseQuery(token)
|
pathData, err := url.ParseQuery(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
@ -32,7 +32,7 @@ func NewHCaptchaProvider() *HCaptchaProvider {
|
|||||||
return captcha
|
return captcha
|
||||||
}
|
}
|
||||||
|
|
||||||
func (captcha *HCaptchaProvider) VerifyCaptcha(token, clientSecret string) (bool, error) {
|
func (captcha *HCaptchaProvider) VerifyCaptcha(token, clientId, clientSecret, clientId2 string) (bool, error) {
|
||||||
reqData := url.Values{
|
reqData := url.Values{
|
||||||
"secret": {clientSecret},
|
"secret": {clientSecret},
|
||||||
"response": {token},
|
"response": {token},
|
||||||
|
@ -17,7 +17,7 @@ package captcha
|
|||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
type CaptchaProvider interface {
|
type CaptchaProvider interface {
|
||||||
VerifyCaptcha(token, clientSecret string) (bool, error)
|
VerifyCaptcha(token, clientId, clientSecret, clientId2 string) (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetCaptchaProvider(captchaType string) CaptchaProvider {
|
func GetCaptchaProvider(captchaType string) CaptchaProvider {
|
||||||
@ -43,11 +43,11 @@ func GetCaptchaProvider(captchaType string) CaptchaProvider {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func VerifyCaptchaByCaptchaType(captchaType, token, clientSecret string) (bool, error) {
|
func VerifyCaptchaByCaptchaType(captchaType, token, clientId, clientSecret, clientId2 string) (bool, error) {
|
||||||
provider := GetCaptchaProvider(captchaType)
|
provider := GetCaptchaProvider(captchaType)
|
||||||
if provider == nil {
|
if provider == nil {
|
||||||
return false, fmt.Errorf("invalid captcha provider: %s", captchaType)
|
return false, fmt.Errorf("invalid captcha provider: %s", captchaType)
|
||||||
}
|
}
|
||||||
|
|
||||||
return provider.VerifyCaptcha(token, clientSecret)
|
return provider.VerifyCaptcha(token, clientId, clientSecret, clientId2)
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ func NewReCaptchaProvider() *ReCaptchaProvider {
|
|||||||
return captcha
|
return captcha
|
||||||
}
|
}
|
||||||
|
|
||||||
func (captcha *ReCaptchaProvider) VerifyCaptcha(token, clientSecret string) (bool, error) {
|
func (captcha *ReCaptchaProvider) VerifyCaptcha(token, clientId, clientSecret, clientId2 string) (bool, error) {
|
||||||
reqData := url.Values{
|
reqData := url.Values{
|
||||||
"secret": {clientSecret},
|
"secret": {clientSecret},
|
||||||
"response": {token},
|
"response": {token},
|
||||||
|
@ -32,7 +32,7 @@ func NewCloudflareTurnstileProvider() *CloudflareTurnstileProvider {
|
|||||||
return captcha
|
return captcha
|
||||||
}
|
}
|
||||||
|
|
||||||
func (captcha *CloudflareTurnstileProvider) VerifyCaptcha(token, clientSecret string) (bool, error) {
|
func (captcha *CloudflareTurnstileProvider) VerifyCaptcha(token, clientId, clientSecret, clientId2 string) (bool, error) {
|
||||||
reqData := url.Values{
|
reqData := url.Values{
|
||||||
"secret": {clientSecret},
|
"secret": {clientSecret},
|
||||||
"response": {token},
|
"response": {token},
|
||||||
|
@ -42,6 +42,7 @@ type Response struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Data interface{} `json:"data"`
|
Data interface{} `json:"data"`
|
||||||
Data2 interface{} `json:"data2"`
|
Data2 interface{} `json:"data2"`
|
||||||
|
Data3 interface{} `json:"data3"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Captcha struct {
|
type Captcha struct {
|
||||||
|
@ -132,7 +132,7 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
|
|||||||
if form.Type == ResponseTypeLogin {
|
if form.Type == ResponseTypeLogin {
|
||||||
c.SetSessionUsername(userId)
|
c.SetSessionUsername(userId)
|
||||||
util.LogInfo(c.Ctx, "API: [%s] signed in", userId)
|
util.LogInfo(c.Ctx, "API: [%s] signed in", userId)
|
||||||
resp = &Response{Status: "ok", Msg: "", Data: userId, Data2: user.NeedUpdatePassword}
|
resp = &Response{Status: "ok", Msg: "", Data: userId, Data3: user.NeedUpdatePassword}
|
||||||
} else if form.Type == ResponseTypeCode {
|
} else if form.Type == ResponseTypeCode {
|
||||||
clientId := c.Input().Get("clientId")
|
clientId := c.Input().Get("clientId")
|
||||||
responseType := c.Input().Get("responseType")
|
responseType := c.Input().Get("responseType")
|
||||||
@ -154,7 +154,7 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
|
|||||||
}
|
}
|
||||||
|
|
||||||
resp = codeToResponse(code)
|
resp = codeToResponse(code)
|
||||||
resp.Data2 = user.NeedUpdatePassword
|
resp.Data3 = user.NeedUpdatePassword
|
||||||
if application.EnableSigninSession || application.HasPromptPage() {
|
if application.EnableSigninSession || application.HasPromptPage() {
|
||||||
// The prompt page needs the user to be signed in
|
// The prompt page needs the user to be signed in
|
||||||
c.SetSessionUsername(userId)
|
c.SetSessionUsername(userId)
|
||||||
@ -168,7 +168,7 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
|
|||||||
token, _ := object.GetTokenByUser(application, user, scope, nonce, c.Ctx.Request.Host)
|
token, _ := object.GetTokenByUser(application, user, scope, nonce, c.Ctx.Request.Host)
|
||||||
resp = tokenToResponse(token)
|
resp = tokenToResponse(token)
|
||||||
|
|
||||||
resp.Data2 = user.NeedUpdatePassword
|
resp.Data3 = user.NeedUpdatePassword
|
||||||
}
|
}
|
||||||
} else if form.Type == ResponseTypeDevice {
|
} else if form.Type == ResponseTypeDevice {
|
||||||
authCache, ok := object.DeviceAuthMap.LoadAndDelete(form.UserCode)
|
authCache, ok := object.DeviceAuthMap.LoadAndDelete(form.UserCode)
|
||||||
@ -195,14 +195,14 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
|
|||||||
|
|
||||||
object.DeviceAuthMap.Store(authCacheCast.UserName, deviceAuthCacheDeviceCodeCast)
|
object.DeviceAuthMap.Store(authCacheCast.UserName, deviceAuthCacheDeviceCodeCast)
|
||||||
|
|
||||||
resp = &Response{Status: "ok", Msg: "", Data: userId, Data2: user.NeedUpdatePassword}
|
resp = &Response{Status: "ok", Msg: "", Data: userId, Data3: user.NeedUpdatePassword}
|
||||||
} else if form.Type == ResponseTypeSaml { // saml flow
|
} else if form.Type == ResponseTypeSaml { // saml flow
|
||||||
res, redirectUrl, method, err := object.GetSamlResponse(application, user, form.SamlRequest, c.Ctx.Request.Host)
|
res, redirectUrl, method, err := object.GetSamlResponse(application, user, form.SamlRequest, c.Ctx.Request.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error(), nil)
|
c.ResponseError(err.Error(), nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
resp = &Response{Status: "ok", Msg: "", Data: res, Data2: map[string]interface{}{"redirectUrl": redirectUrl, "method": method, "needUpdatePassword": user.NeedUpdatePassword}}
|
resp = &Response{Status: "ok", Msg: "", Data: res, Data2: map[string]interface{}{"redirectUrl": redirectUrl, "method": method}, Data3: user.NeedUpdatePassword}
|
||||||
|
|
||||||
if application.EnableSigninSession || application.HasPromptPage() {
|
if application.EnableSigninSession || application.HasPromptPage() {
|
||||||
// The prompt page needs the user to be signed in
|
// The prompt page needs the user to be signed in
|
||||||
@ -504,6 +504,8 @@ func (c *ApiController) Login() {
|
|||||||
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), authForm.CountryCode))
|
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), authForm.CountryCode))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
} else if verificationCodeType == object.VerifyTypeEmail {
|
||||||
|
checkDest = authForm.Username
|
||||||
}
|
}
|
||||||
|
|
||||||
// check result through Email or Phone
|
// check result through Email or Phone
|
||||||
@ -553,8 +555,11 @@ func (c *ApiController) Login() {
|
|||||||
c.ResponseError(c.T("auth:The login method: login with LDAP is not enabled for the application"))
|
c.ResponseError(c.T("auth:The login method: login with LDAP is not enabled for the application"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
|
||||||
|
|
||||||
var enableCaptcha bool
|
var enableCaptcha bool
|
||||||
if enableCaptcha, err = object.CheckToEnableCaptcha(application, authForm.Organization, authForm.Username); err != nil {
|
if enableCaptcha, err = object.CheckToEnableCaptcha(application, authForm.Organization, authForm.Username, clientIp); err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
} else if enableCaptcha {
|
} else if enableCaptcha {
|
||||||
@ -569,7 +574,7 @@ func (c *ApiController) Login() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var isHuman bool
|
var isHuman bool
|
||||||
isHuman, err = captcha.VerifyCaptchaByCaptchaType(authForm.CaptchaType, authForm.CaptchaToken, authForm.ClientSecret)
|
isHuman, err = captcha.VerifyCaptchaByCaptchaType(authForm.CaptchaType, authForm.CaptchaToken, captchaProvider.ClientId, authForm.ClientSecret, captchaProvider.ClientId2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
@ -1220,27 +1225,26 @@ func (c *ApiController) GetQRCode() {
|
|||||||
func (c *ApiController) GetCaptchaStatus() {
|
func (c *ApiController) GetCaptchaStatus() {
|
||||||
organization := c.Input().Get("organization")
|
organization := c.Input().Get("organization")
|
||||||
userId := c.Input().Get("userId")
|
userId := c.Input().Get("userId")
|
||||||
user, err := object.GetUserByFields(organization, userId)
|
applicationName := c.Input().Get("application")
|
||||||
|
|
||||||
|
application, err := object.GetApplication(fmt.Sprintf("admin/%s", applicationName))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if application == nil {
|
||||||
captchaEnabled := false
|
c.ResponseError("application not found")
|
||||||
if user != nil {
|
return
|
||||||
var failedSigninLimit int
|
|
||||||
failedSigninLimit, _, err = object.GetFailedSigninConfigByUser(user)
|
|
||||||
if err != nil {
|
|
||||||
c.ResponseError(err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if user.SigninWrongTimes >= failedSigninLimit {
|
|
||||||
captchaEnabled = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
|
||||||
|
captchaEnabled, err := object.CheckToEnableCaptcha(application, organization, userId, clientIp)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
c.ResponseOk(captchaEnabled)
|
c.ResponseOk(captchaEnabled)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callback
|
// Callback
|
||||||
|
56
controllers/group_upload.go
Normal file
56
controllers/group_upload.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// Copyright 2025 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *ApiController) UploadGroups() {
|
||||||
|
userId := c.GetSessionUsername()
|
||||||
|
owner, user := util.GetOwnerAndNameFromId(userId)
|
||||||
|
|
||||||
|
file, header, err := c.Ctx.Request.FormFile("file")
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fileId := fmt.Sprintf("%s_%s_%s", owner, user, util.RemoveExt(header.Filename))
|
||||||
|
path := util.GetUploadXlsxPath(fileId)
|
||||||
|
defer os.Remove(path)
|
||||||
|
|
||||||
|
err = saveFile(path, &file)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
affected, err := object.UploadGroups(owner, path)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if affected {
|
||||||
|
c.ResponseOk()
|
||||||
|
} else {
|
||||||
|
c.ResponseError(c.T("general:Failed to import groups"))
|
||||||
|
}
|
||||||
|
}
|
@ -49,6 +49,6 @@ func (c *ApiController) UploadPermissions() {
|
|||||||
if affected {
|
if affected {
|
||||||
c.ResponseOk()
|
c.ResponseOk()
|
||||||
} else {
|
} else {
|
||||||
c.ResponseError(c.T("user_upload:Failed to import users"))
|
c.ResponseError(c.T("general:Failed to import users"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -182,7 +182,7 @@ func (c *ApiController) BuyProduct() {
|
|||||||
paidUserName := c.Input().Get("userName")
|
paidUserName := c.Input().Get("userName")
|
||||||
owner, _ := util.GetOwnerAndNameFromId(id)
|
owner, _ := util.GetOwnerAndNameFromId(id)
|
||||||
userId := util.GetId(owner, paidUserName)
|
userId := util.GetId(owner, paidUserName)
|
||||||
if paidUserName != "" && !c.IsAdmin() {
|
if paidUserName != "" && paidUserName != c.GetSessionUsername() && !c.IsAdmin() {
|
||||||
c.ResponseError(c.T("general:Only admin user can specify user"))
|
c.ResponseError(c.T("general:Only admin user can specify user"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -49,6 +49,6 @@ func (c *ApiController) UploadRoles() {
|
|||||||
if affected {
|
if affected {
|
||||||
c.ResponseOk()
|
c.ResponseOk()
|
||||||
} else {
|
} else {
|
||||||
c.ResponseError(c.T("user_upload:Failed to import users"))
|
c.ResponseError(c.T("general:Failed to import users"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -197,8 +197,8 @@ func (c *ApiController) GetUser() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var organization *object.Organization
|
||||||
if user != nil {
|
if user != nil {
|
||||||
var organization *object.Organization
|
|
||||||
organization, err = object.GetOrganizationByUser(user)
|
organization, err = object.GetOrganizationByUser(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
@ -237,6 +237,14 @@ func (c *ApiController) GetUser() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if organization != nil && user != nil {
|
||||||
|
user, err = object.GetFilteredUser(user, c.IsAdmin(), c.IsAdminOrSelf(user), organization.AccountItems)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
c.ResponseOk(user)
|
c.ResponseOk(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -282,13 +290,6 @@ func (c *ApiController) UpdateUser() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Input().Get("allowEmpty") == "" {
|
|
||||||
if user.DisplayName == "" {
|
|
||||||
c.ResponseError(c.T("user:Display name cannot be empty"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if user.MfaEmailEnabled && user.Email == "" {
|
if user.MfaEmailEnabled && user.Email == "" {
|
||||||
c.ResponseError(c.T("user:MFA email is enabled but email is empty"))
|
c.ResponseError(c.T("user:MFA email is enabled but email is empty"))
|
||||||
return
|
return
|
||||||
@ -310,7 +311,8 @@ func (c *ApiController) UpdateUser() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isAdmin := c.IsAdmin()
|
isAdmin := c.IsAdmin()
|
||||||
if pass, err := object.CheckPermissionForUpdateUser(oldUser, &user, isAdmin, c.GetAcceptLanguage()); !pass {
|
allowDisplayNameEmpty := c.Input().Get("allowEmpty") != ""
|
||||||
|
if pass, err := object.CheckPermissionForUpdateUser(oldUser, &user, isAdmin, allowDisplayNameEmpty, c.GetAcceptLanguage()); !pass {
|
||||||
c.ResponseError(err)
|
c.ResponseError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -572,7 +574,7 @@ func (c *ApiController) SetPassword() {
|
|||||||
targetUser.LastChangePasswordTime = util.GetCurrentTime()
|
targetUser.LastChangePasswordTime = util.GetCurrentTime()
|
||||||
|
|
||||||
if user.Ldap == "" {
|
if user.Ldap == "" {
|
||||||
_, err = object.UpdateUser(userId, targetUser, []string{"password", "need_update_password", "password_type", "last_change_password_time"}, false)
|
_, err = object.UpdateUser(userId, targetUser, []string{"password", "password_salt", "need_update_password", "password_type", "last_change_password_time"}, false)
|
||||||
} else {
|
} else {
|
||||||
if isAdmin {
|
if isAdmin {
|
||||||
err = object.ResetLdapPassword(targetUser, "", newPassword, c.GetAcceptLanguage())
|
err = object.ResetLdapPassword(targetUser, "", newPassword, c.GetAcceptLanguage())
|
||||||
|
@ -67,6 +67,6 @@ func (c *ApiController) UploadUsers() {
|
|||||||
if affected {
|
if affected {
|
||||||
c.ResponseOk()
|
c.ResponseOk()
|
||||||
} else {
|
} else {
|
||||||
c.ResponseError(c.T("user_upload:Failed to import users"))
|
c.ResponseError(c.T("general:Failed to import users"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -160,7 +160,7 @@ func (c *ApiController) SendVerificationCode() {
|
|||||||
if captchaProvider := captcha.GetCaptchaProvider(vform.CaptchaType); captchaProvider == nil {
|
if captchaProvider := captcha.GetCaptchaProvider(vform.CaptchaType); captchaProvider == nil {
|
||||||
c.ResponseError(c.T("general:don't support captchaProvider: ") + vform.CaptchaType)
|
c.ResponseError(c.T("general:don't support captchaProvider: ") + vform.CaptchaType)
|
||||||
return
|
return
|
||||||
} else if isHuman, err := captchaProvider.VerifyCaptcha(vform.CaptchaToken, vform.ClientSecret); err != nil {
|
} else if isHuman, err := captchaProvider.VerifyCaptcha(vform.CaptchaToken, provider.ClientId, vform.ClientSecret, provider.ClientId2); err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
} else if !isHuman {
|
} else if !isHuman {
|
||||||
@ -349,7 +349,7 @@ func (c *ApiController) VerifyCaptcha() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
isValid, err := provider.VerifyCaptcha(vform.CaptchaToken, vform.ClientSecret)
|
isValid, err := provider.VerifyCaptcha(vform.CaptchaToken, captchaProvider.ClientId, vform.ClientSecret, captchaProvider.ClientId2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
|
@ -23,7 +23,7 @@ func NewArgon2idCredManager() *Argon2idCredManager {
|
|||||||
return cm
|
return cm
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cm *Argon2idCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
func (cm *Argon2idCredManager) GetHashedPassword(password string, salt string) string {
|
||||||
hash, err := argon2id.CreateHash(password, argon2id.DefaultParams)
|
hash, err := argon2id.CreateHash(password, argon2id.DefaultParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
@ -31,7 +31,7 @@ func (cm *Argon2idCredManager) GetHashedPassword(password string, userSalt strin
|
|||||||
return hash
|
return hash
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cm *Argon2idCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
|
func (cm *Argon2idCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, salt string) bool {
|
||||||
match, _ := argon2id.ComparePasswordAndHash(plainPwd, hashedPwd)
|
match, _ := argon2id.ComparePasswordAndHash(plainPwd, hashedPwd)
|
||||||
return match
|
return match
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ func NewBcryptCredManager() *BcryptCredManager {
|
|||||||
return cm
|
return cm
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cm *BcryptCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
func (cm *BcryptCredManager) GetHashedPassword(password string, salt string) string {
|
||||||
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
@ -17,7 +17,7 @@ func (cm *BcryptCredManager) GetHashedPassword(password string, userSalt string,
|
|||||||
return string(bytes)
|
return string(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cm *BcryptCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
|
func (cm *BcryptCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, salt string) bool {
|
||||||
err := bcrypt.CompareHashAndPassword([]byte(hashedPwd), []byte(plainPwd))
|
err := bcrypt.CompareHashAndPassword([]byte(hashedPwd), []byte(plainPwd))
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
@ -15,8 +15,8 @@
|
|||||||
package cred
|
package cred
|
||||||
|
|
||||||
type CredManager interface {
|
type CredManager interface {
|
||||||
GetHashedPassword(password string, userSalt string, organizationSalt string) string
|
GetHashedPassword(password string, salt string) string
|
||||||
IsPasswordCorrect(password string, passwordHash string, userSalt string, organizationSalt string) bool
|
IsPasswordCorrect(password string, passwordHash string, salt string) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetCredManager(passwordType string) CredManager {
|
func GetCredManager(passwordType string) CredManager {
|
||||||
|
@ -37,14 +37,10 @@ func NewMd5UserSaltCredManager() *Md5UserSaltCredManager {
|
|||||||
return cm
|
return cm
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cm *Md5UserSaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
func (cm *Md5UserSaltCredManager) GetHashedPassword(password string, salt string) string {
|
||||||
res := getMd5HexDigest(password)
|
return getMd5HexDigest(getMd5HexDigest(password) + salt)
|
||||||
if userSalt != "" {
|
|
||||||
res = getMd5HexDigest(res + userSalt)
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cm *Md5UserSaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
|
func (cm *Md5UserSaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, salt string) bool {
|
||||||
return hashedPwd == cm.GetHashedPassword(plainPwd, userSalt, organizationSalt)
|
return hashedPwd == cm.GetHashedPassword(plainPwd, salt)
|
||||||
}
|
}
|
||||||
|
@ -28,13 +28,13 @@ func NewPbkdf2SaltCredManager() *Pbkdf2SaltCredManager {
|
|||||||
return cm
|
return cm
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cm *Pbkdf2SaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
func (cm *Pbkdf2SaltCredManager) GetHashedPassword(password string, salt string) string {
|
||||||
// https://www.keycloak.org/docs/latest/server_admin/index.html#password-database-compromised
|
// https://www.keycloak.org/docs/latest/server_admin/index.html#password-database-compromised
|
||||||
decodedSalt, _ := base64.StdEncoding.DecodeString(userSalt)
|
decodedSalt, _ := base64.StdEncoding.DecodeString(salt)
|
||||||
res := pbkdf2.Key([]byte(password), decodedSalt, 27500, 64, sha256.New)
|
res := pbkdf2.Key([]byte(password), decodedSalt, 27500, 64, sha256.New)
|
||||||
return base64.StdEncoding.EncodeToString(res)
|
return base64.StdEncoding.EncodeToString(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cm *Pbkdf2SaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
|
func (cm *Pbkdf2SaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, salt string) bool {
|
||||||
return hashedPwd == cm.GetHashedPassword(plainPwd, userSalt, organizationSalt)
|
return hashedPwd == cm.GetHashedPassword(plainPwd, salt)
|
||||||
}
|
}
|
||||||
|
@ -32,12 +32,8 @@ func NewPbkdf2DjangoCredManager() *Pbkdf2DjangoCredManager {
|
|||||||
return cm
|
return cm
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Pbkdf2DjangoCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
func (m *Pbkdf2DjangoCredManager) GetHashedPassword(password string, salt string) string {
|
||||||
iterations := 260000
|
iterations := 260000
|
||||||
salt := userSalt
|
|
||||||
if salt == "" {
|
|
||||||
salt = organizationSalt
|
|
||||||
}
|
|
||||||
|
|
||||||
saltBytes := []byte(salt)
|
saltBytes := []byte(salt)
|
||||||
passwordBytes := []byte(password)
|
passwordBytes := []byte(password)
|
||||||
@ -46,7 +42,7 @@ func (m *Pbkdf2DjangoCredManager) GetHashedPassword(password string, userSalt st
|
|||||||
return "pbkdf2_sha256$" + strconv.Itoa(iterations) + "$" + salt + "$" + hashBase64
|
return "pbkdf2_sha256$" + strconv.Itoa(iterations) + "$" + salt + "$" + hashBase64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Pbkdf2DjangoCredManager) IsPasswordCorrect(password string, passwordHash string, userSalt string, organizationSalt string) bool {
|
func (m *Pbkdf2DjangoCredManager) IsPasswordCorrect(password string, passwordHash string, _salt string) bool {
|
||||||
parts := strings.Split(passwordHash, "$")
|
parts := strings.Split(passwordHash, "$")
|
||||||
if len(parts) != 4 {
|
if len(parts) != 4 {
|
||||||
return false
|
return false
|
||||||
|
@ -21,10 +21,10 @@ func NewPlainCredManager() *PlainCredManager {
|
|||||||
return cm
|
return cm
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cm *PlainCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
func (cm *PlainCredManager) GetHashedPassword(password string, salt string) string {
|
||||||
return password
|
return password
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cm *PlainCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
|
func (cm *PlainCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, salt string) bool {
|
||||||
return hashedPwd == plainPwd
|
return hashedPwd == plainPwd
|
||||||
}
|
}
|
||||||
|
@ -37,14 +37,10 @@ func NewSha256SaltCredManager() *Sha256SaltCredManager {
|
|||||||
return cm
|
return cm
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cm *Sha256SaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
func (cm *Sha256SaltCredManager) GetHashedPassword(password string, salt string) string {
|
||||||
res := getSha256HexDigest(password)
|
return getSha256HexDigest(getSha256HexDigest(password) + salt)
|
||||||
if organizationSalt != "" {
|
|
||||||
res = getSha256HexDigest(res + organizationSalt)
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cm *Sha256SaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
|
func (cm *Sha256SaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, salt string) bool {
|
||||||
return hashedPwd == cm.GetHashedPassword(plainPwd, userSalt, organizationSalt)
|
return hashedPwd == cm.GetHashedPassword(plainPwd, salt)
|
||||||
}
|
}
|
||||||
|
@ -23,12 +23,12 @@ func TestGetSaltedPassword(t *testing.T) {
|
|||||||
password := "123456"
|
password := "123456"
|
||||||
salt := "123"
|
salt := "123"
|
||||||
cm := NewSha256SaltCredManager()
|
cm := NewSha256SaltCredManager()
|
||||||
fmt.Printf("%s -> %s\n", password, cm.GetHashedPassword(password, "", salt))
|
fmt.Printf("%s -> %s\n", password, cm.GetHashedPassword(password, salt))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetPassword(t *testing.T) {
|
func TestGetPassword(t *testing.T) {
|
||||||
password := "123456"
|
password := "123456"
|
||||||
cm := NewSha256SaltCredManager()
|
cm := NewSha256SaltCredManager()
|
||||||
// https://passwordsgenerator.net/sha256-hash-generator/
|
// https://passwordsgenerator.net/sha256-hash-generator/
|
||||||
fmt.Printf("%s -> %s\n", "8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92", cm.GetHashedPassword(password, "", ""))
|
fmt.Printf("%s -> %s\n", "8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92", cm.GetHashedPassword(password, ""))
|
||||||
}
|
}
|
||||||
|
@ -37,14 +37,10 @@ func NewSha512SaltCredManager() *Sha512SaltCredManager {
|
|||||||
return cm
|
return cm
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cm *Sha512SaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
func (cm *Sha512SaltCredManager) GetHashedPassword(password string, salt string) string {
|
||||||
res := getSha512HexDigest(password)
|
return getSha512HexDigest(getSha512HexDigest(password) + salt)
|
||||||
if organizationSalt != "" {
|
|
||||||
res = getSha512HexDigest(res + organizationSalt)
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cm *Sha512SaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
|
func (cm *Sha512SaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, salt string) bool {
|
||||||
return hashedPwd == cm.GetHashedPassword(plainPwd, userSalt, organizationSalt)
|
return hashedPwd == cm.GetHashedPassword(plainPwd, salt)
|
||||||
}
|
}
|
||||||
|
2
go.mod
2
go.mod
@ -7,6 +7,7 @@ require (
|
|||||||
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387
|
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387
|
||||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.4
|
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.4
|
||||||
github.com/alibabacloud-go/facebody-20191230/v5 v5.1.2
|
github.com/alibabacloud-go/facebody-20191230/v5 v5.1.2
|
||||||
|
github.com/alibabacloud-go/openapi-util v0.1.0
|
||||||
github.com/alibabacloud-go/tea v1.3.2
|
github.com/alibabacloud-go/tea v1.3.2
|
||||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.7
|
github.com/alibabacloud-go/tea-utils/v2 v2.0.7
|
||||||
github.com/aws/aws-sdk-go v1.45.5
|
github.com/aws/aws-sdk-go v1.45.5
|
||||||
@ -90,7 +91,6 @@ require (
|
|||||||
github.com/alibabacloud-go/darabonba-number v1.0.4 // indirect
|
github.com/alibabacloud-go/darabonba-number v1.0.4 // indirect
|
||||||
github.com/alibabacloud-go/debug v1.0.1 // indirect
|
github.com/alibabacloud-go/debug v1.0.1 // indirect
|
||||||
github.com/alibabacloud-go/endpoint-util v1.1.0 // indirect
|
github.com/alibabacloud-go/endpoint-util v1.1.0 // indirect
|
||||||
github.com/alibabacloud-go/openapi-util v0.1.0 // indirect
|
|
||||||
github.com/alibabacloud-go/openplatform-20191219/v2 v2.0.1 // indirect
|
github.com/alibabacloud-go/openplatform-20191219/v2 v2.0.1 // indirect
|
||||||
github.com/alibabacloud-go/tea-fileform v1.1.1 // indirect
|
github.com/alibabacloud-go/tea-fileform v1.1.1 // indirect
|
||||||
github.com/alibabacloud-go/tea-oss-sdk v1.1.3 // indirect
|
github.com/alibabacloud-go/tea-oss-sdk v1.1.3 // indirect
|
||||||
|
@ -92,6 +92,8 @@
|
|||||||
"the adapter: %s is not found": "the adapter: %s is not found"
|
"the adapter: %s is not found": "the adapter: %s is not found"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
"Failed to import groups": "Failed to import groups",
|
||||||
|
"Failed to import users": "Failed to import users",
|
||||||
"Missing parameter": "Missing parameter",
|
"Missing parameter": "Missing parameter",
|
||||||
"Only admin user can specify user": "Only admin user can specify user",
|
"Only admin user can specify user": "Only admin user can specify user",
|
||||||
"Please login first": "Please login first",
|
"Please login first": "Please login first",
|
||||||
@ -162,9 +164,6 @@
|
|||||||
"New password cannot contain blank space.": "New password cannot contain blank space.",
|
"New password cannot contain blank space.": "New password cannot contain blank space.",
|
||||||
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
||||||
},
|
},
|
||||||
"user_upload": {
|
|
||||||
"Failed to import users": "Failed to import users"
|
|
||||||
},
|
|
||||||
"util": {
|
"util": {
|
||||||
"No application is found for userId: %s": "No application is found for userId: %s",
|
"No application is found for userId: %s": "No application is found for userId: %s",
|
||||||
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
||||||
|
@ -92,6 +92,8 @@
|
|||||||
"the adapter: %s is not found": "the adapter: %s is not found"
|
"the adapter: %s is not found": "the adapter: %s is not found"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
"Failed to import groups": "Failed to import groups",
|
||||||
|
"Failed to import users": "Nepodařilo se importovat uživatele",
|
||||||
"Missing parameter": "Chybějící parametr",
|
"Missing parameter": "Chybějící parametr",
|
||||||
"Only admin user can specify user": "Only admin user can specify user",
|
"Only admin user can specify user": "Only admin user can specify user",
|
||||||
"Please login first": "Prosím, přihlaste se nejprve",
|
"Please login first": "Prosím, přihlaste se nejprve",
|
||||||
@ -162,9 +164,6 @@
|
|||||||
"New password cannot contain blank space.": "Nové heslo nemůže obsahovat prázdné místo.",
|
"New password cannot contain blank space.": "Nové heslo nemůže obsahovat prázdné místo.",
|
||||||
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
||||||
},
|
},
|
||||||
"user_upload": {
|
|
||||||
"Failed to import users": "Nepodařilo se importovat uživatele"
|
|
||||||
},
|
|
||||||
"util": {
|
"util": {
|
||||||
"No application is found for userId: %s": "Pro userId: %s nebyla nalezena žádná aplikace",
|
"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",
|
"No provider for category: %s is found for application: %s": "Pro kategorii: %s nebyl nalezen žádný poskytovatel pro aplikaci: %s",
|
||||||
|
@ -92,6 +92,8 @@
|
|||||||
"the adapter: %s is not found": "the adapter: %s is not found"
|
"the adapter: %s is not found": "the adapter: %s is not found"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
"Failed to import groups": "Failed to import groups",
|
||||||
|
"Failed to import users": "Fehler beim Importieren von Benutzern",
|
||||||
"Missing parameter": "Fehlender Parameter",
|
"Missing parameter": "Fehlender Parameter",
|
||||||
"Only admin user can specify user": "Only admin user can specify user",
|
"Only admin user can specify user": "Only admin user can specify user",
|
||||||
"Please login first": "Bitte zuerst einloggen",
|
"Please login first": "Bitte zuerst einloggen",
|
||||||
@ -162,9 +164,6 @@
|
|||||||
"New password cannot contain blank space.": "Das neue Passwort darf keine Leerzeichen enthalten.",
|
"New password cannot contain blank space.": "Das neue Passwort darf keine Leerzeichen enthalten.",
|
||||||
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
||||||
},
|
},
|
||||||
"user_upload": {
|
|
||||||
"Failed to import users": "Fehler beim Importieren von Benutzern"
|
|
||||||
},
|
|
||||||
"util": {
|
"util": {
|
||||||
"No application is found for userId: %s": "Es wurde keine Anwendung für die Benutzer-ID gefunden: %s",
|
"No application is found for userId: %s": "Es wurde keine Anwendung für die Benutzer-ID gefunden: %s",
|
||||||
"No provider for category: %s is found for application: %s": "Kein Anbieter für die Kategorie %s gefunden für die Anwendung: %s",
|
"No provider for category: %s is found for application: %s": "Kein Anbieter für die Kategorie %s gefunden für die Anwendung: %s",
|
||||||
|
@ -92,6 +92,8 @@
|
|||||||
"the adapter: %s is not found": "the adapter: %s is not found"
|
"the adapter: %s is not found": "the adapter: %s is not found"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
"Failed to import groups": "Failed to import groups",
|
||||||
|
"Failed to import users": "Failed to import users",
|
||||||
"Missing parameter": "Missing parameter",
|
"Missing parameter": "Missing parameter",
|
||||||
"Only admin user can specify user": "Only admin user can specify user",
|
"Only admin user can specify user": "Only admin user can specify user",
|
||||||
"Please login first": "Please login first",
|
"Please login first": "Please login first",
|
||||||
@ -162,9 +164,6 @@
|
|||||||
"New password cannot contain blank space.": "New password cannot contain blank space.",
|
"New password cannot contain blank space.": "New password cannot contain blank space.",
|
||||||
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
||||||
},
|
},
|
||||||
"user_upload": {
|
|
||||||
"Failed to import users": "Failed to import users"
|
|
||||||
},
|
|
||||||
"util": {
|
"util": {
|
||||||
"No application is found for userId: %s": "No application is found for userId: %s",
|
"No application is found for userId: %s": "No application is found for userId: %s",
|
||||||
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
||||||
|
@ -92,6 +92,8 @@
|
|||||||
"the adapter: %s is not found": "the adapter: %s is not found"
|
"the adapter: %s is not found": "the adapter: %s is not found"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
"Failed to import groups": "Failed to import groups",
|
||||||
|
"Failed to import users": "Error al importar usuarios",
|
||||||
"Missing parameter": "Parámetro faltante",
|
"Missing parameter": "Parámetro faltante",
|
||||||
"Only admin user can specify user": "Only admin user can specify user",
|
"Only admin user can specify user": "Only admin user can specify user",
|
||||||
"Please login first": "Por favor, inicia sesión primero",
|
"Please login first": "Por favor, inicia sesión primero",
|
||||||
@ -162,9 +164,6 @@
|
|||||||
"New password cannot contain blank space.": "La nueva contraseña no puede contener espacios en blanco.",
|
"New password cannot contain blank space.": "La nueva contraseña no puede contener espacios en blanco.",
|
||||||
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
||||||
},
|
},
|
||||||
"user_upload": {
|
|
||||||
"Failed to import users": "Error al importar usuarios"
|
|
||||||
},
|
|
||||||
"util": {
|
"util": {
|
||||||
"No application is found for userId: %s": "No se encuentra ninguna aplicación para el Id de usuario: %s",
|
"No application is found for userId: %s": "No se encuentra ninguna aplicación para el Id de usuario: %s",
|
||||||
"No provider for category: %s is found for application: %s": "No se encuentra un proveedor para la categoría: %s para la aplicación: %s",
|
"No provider for category: %s is found for application: %s": "No se encuentra un proveedor para la categoría: %s para la aplicación: %s",
|
||||||
|
@ -92,6 +92,8 @@
|
|||||||
"the adapter: %s is not found": "the adapter: %s is not found"
|
"the adapter: %s is not found": "the adapter: %s is not found"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
"Failed to import groups": "Failed to import groups",
|
||||||
|
"Failed to import users": "عدم موفقیت در وارد کردن کاربران",
|
||||||
"Missing parameter": "پارامتر گمشده",
|
"Missing parameter": "پارامتر گمشده",
|
||||||
"Only admin user can specify user": "Only admin user can specify user",
|
"Only admin user can specify user": "Only admin user can specify user",
|
||||||
"Please login first": "لطفاً ابتدا وارد شوید",
|
"Please login first": "لطفاً ابتدا وارد شوید",
|
||||||
@ -162,9 +164,6 @@
|
|||||||
"New password cannot contain blank space.": "رمز عبور جدید نمیتواند حاوی فاصله خالی باشد.",
|
"New password cannot contain blank space.": "رمز عبور جدید نمیتواند حاوی فاصله خالی باشد.",
|
||||||
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
||||||
},
|
},
|
||||||
"user_upload": {
|
|
||||||
"Failed to import users": "عدم موفقیت در وارد کردن کاربران"
|
|
||||||
},
|
|
||||||
"util": {
|
"util": {
|
||||||
"No application is found for userId: %s": "هیچ برنامهای برای userId: %s یافت نشد",
|
"No application is found for userId: %s": "هیچ برنامهای برای userId: %s یافت نشد",
|
||||||
"No provider for category: %s is found for application: %s": "هیچ ارائهدهندهای برای دستهبندی: %s برای برنامه: %s یافت نشد",
|
"No provider for category: %s is found for application: %s": "هیچ ارائهدهندهای برای دستهبندی: %s برای برنامه: %s یافت نشد",
|
||||||
|
@ -92,6 +92,8 @@
|
|||||||
"the adapter: %s is not found": "the adapter: %s is not found"
|
"the adapter: %s is not found": "the adapter: %s is not found"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
"Failed to import groups": "Failed to import groups",
|
||||||
|
"Failed to import users": "Failed to import users",
|
||||||
"Missing parameter": "Missing parameter",
|
"Missing parameter": "Missing parameter",
|
||||||
"Only admin user can specify user": "Only admin user can specify user",
|
"Only admin user can specify user": "Only admin user can specify user",
|
||||||
"Please login first": "Please login first",
|
"Please login first": "Please login first",
|
||||||
@ -162,9 +164,6 @@
|
|||||||
"New password cannot contain blank space.": "New password cannot contain blank space.",
|
"New password cannot contain blank space.": "New password cannot contain blank space.",
|
||||||
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
||||||
},
|
},
|
||||||
"user_upload": {
|
|
||||||
"Failed to import users": "Failed to import users"
|
|
||||||
},
|
|
||||||
"util": {
|
"util": {
|
||||||
"No application is found for userId: %s": "No application is found for userId: %s",
|
"No application is found for userId: %s": "No application is found for userId: %s",
|
||||||
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
||||||
|
@ -92,6 +92,8 @@
|
|||||||
"the adapter: %s is not found": "the adapter: %s is not found"
|
"the adapter: %s is not found": "the adapter: %s is not found"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
"Failed to import groups": "Failed to import groups",
|
||||||
|
"Failed to import users": "Échec de l'importation des utilisateurs",
|
||||||
"Missing parameter": "Paramètre manquant",
|
"Missing parameter": "Paramètre manquant",
|
||||||
"Only admin user can specify user": "Only admin user can specify user",
|
"Only admin user can specify user": "Only admin user can specify user",
|
||||||
"Please login first": "Veuillez d'abord vous connecter",
|
"Please login first": "Veuillez d'abord vous connecter",
|
||||||
@ -162,9 +164,6 @@
|
|||||||
"New password cannot contain blank space.": "Le nouveau mot de passe ne peut pas contenir d'espace.",
|
"New password cannot contain blank space.": "Le nouveau mot de passe ne peut pas contenir d'espace.",
|
||||||
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
||||||
},
|
},
|
||||||
"user_upload": {
|
|
||||||
"Failed to import users": "Échec de l'importation des utilisateurs"
|
|
||||||
},
|
|
||||||
"util": {
|
"util": {
|
||||||
"No application is found for userId: %s": "Aucune application n'a été trouvée pour l'identifiant d'utilisateur : %s",
|
"No application is found for userId: %s": "Aucune application n'a été trouvée pour l'identifiant d'utilisateur : %s",
|
||||||
"No provider for category: %s is found for application: %s": "Aucun fournisseur pour la catégorie: %s n'est trouvé pour l'application: %s",
|
"No provider for category: %s is found for application: %s": "Aucun fournisseur pour la catégorie: %s n'est trouvé pour l'application: %s",
|
||||||
|
@ -92,6 +92,8 @@
|
|||||||
"the adapter: %s is not found": "the adapter: %s is not found"
|
"the adapter: %s is not found": "the adapter: %s is not found"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
"Failed to import groups": "Failed to import groups",
|
||||||
|
"Failed to import users": "Failed to import users",
|
||||||
"Missing parameter": "Missing parameter",
|
"Missing parameter": "Missing parameter",
|
||||||
"Only admin user can specify user": "Only admin user can specify user",
|
"Only admin user can specify user": "Only admin user can specify user",
|
||||||
"Please login first": "Please login first",
|
"Please login first": "Please login first",
|
||||||
@ -162,9 +164,6 @@
|
|||||||
"New password cannot contain blank space.": "New password cannot contain blank space.",
|
"New password cannot contain blank space.": "New password cannot contain blank space.",
|
||||||
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
||||||
},
|
},
|
||||||
"user_upload": {
|
|
||||||
"Failed to import users": "Failed to import users"
|
|
||||||
},
|
|
||||||
"util": {
|
"util": {
|
||||||
"No application is found for userId: %s": "No application is found for userId: %s",
|
"No application is found for userId: %s": "No application is found for userId: %s",
|
||||||
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
||||||
|
@ -92,6 +92,8 @@
|
|||||||
"the adapter: %s is not found": "the adapter: %s is not found"
|
"the adapter: %s is not found": "the adapter: %s is not found"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
"Failed to import groups": "Failed to import groups",
|
||||||
|
"Failed to import users": "Gagal mengimpor pengguna",
|
||||||
"Missing parameter": "Parameter hilang",
|
"Missing parameter": "Parameter hilang",
|
||||||
"Only admin user can specify user": "Only admin user can specify user",
|
"Only admin user can specify user": "Only admin user can specify user",
|
||||||
"Please login first": "Silahkan login terlebih dahulu",
|
"Please login first": "Silahkan login terlebih dahulu",
|
||||||
@ -162,9 +164,6 @@
|
|||||||
"New password cannot contain blank space.": "Sandi baru tidak boleh mengandung spasi kosong.",
|
"New password cannot contain blank space.": "Sandi baru tidak boleh mengandung spasi kosong.",
|
||||||
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
||||||
},
|
},
|
||||||
"user_upload": {
|
|
||||||
"Failed to import users": "Gagal mengimpor pengguna"
|
|
||||||
},
|
|
||||||
"util": {
|
"util": {
|
||||||
"No application is found for userId: %s": "Tidak ditemukan aplikasi untuk userId: %s",
|
"No application is found for userId: %s": "Tidak ditemukan aplikasi untuk userId: %s",
|
||||||
"No provider for category: %s is found for application: %s": "Tidak ditemukan penyedia untuk kategori: %s untuk aplikasi: %s",
|
"No provider for category: %s is found for application: %s": "Tidak ditemukan penyedia untuk kategori: %s untuk aplikasi: %s",
|
||||||
|
@ -92,6 +92,8 @@
|
|||||||
"the adapter: %s is not found": "the adapter: %s is not found"
|
"the adapter: %s is not found": "the adapter: %s is not found"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
"Failed to import groups": "Failed to import groups",
|
||||||
|
"Failed to import users": "Failed to import users",
|
||||||
"Missing parameter": "Missing parameter",
|
"Missing parameter": "Missing parameter",
|
||||||
"Only admin user can specify user": "Only admin user can specify user",
|
"Only admin user can specify user": "Only admin user can specify user",
|
||||||
"Please login first": "Please login first",
|
"Please login first": "Please login first",
|
||||||
@ -162,9 +164,6 @@
|
|||||||
"New password cannot contain blank space.": "New password cannot contain blank space.",
|
"New password cannot contain blank space.": "New password cannot contain blank space.",
|
||||||
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
||||||
},
|
},
|
||||||
"user_upload": {
|
|
||||||
"Failed to import users": "Failed to import users"
|
|
||||||
},
|
|
||||||
"util": {
|
"util": {
|
||||||
"No application is found for userId: %s": "No application is found for userId: %s",
|
"No application is found for userId: %s": "No application is found for userId: %s",
|
||||||
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
||||||
|
@ -92,6 +92,8 @@
|
|||||||
"the adapter: %s is not found": "the adapter: %s is not found"
|
"the adapter: %s is not found": "the adapter: %s is not found"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
"Failed to import groups": "Failed to import groups",
|
||||||
|
"Failed to import users": "ユーザーのインポートに失敗しました",
|
||||||
"Missing parameter": "不足しているパラメーター",
|
"Missing parameter": "不足しているパラメーター",
|
||||||
"Only admin user can specify user": "Only admin user can specify user",
|
"Only admin user can specify user": "Only admin user can specify user",
|
||||||
"Please login first": "最初にログインしてください",
|
"Please login first": "最初にログインしてください",
|
||||||
@ -162,9 +164,6 @@
|
|||||||
"New password cannot contain blank space.": "新しいパスワードにはスペースを含めることはできません。",
|
"New password cannot contain blank space.": "新しいパスワードにはスペースを含めることはできません。",
|
||||||
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
||||||
},
|
},
|
||||||
"user_upload": {
|
|
||||||
"Failed to import users": "ユーザーのインポートに失敗しました"
|
|
||||||
},
|
|
||||||
"util": {
|
"util": {
|
||||||
"No application is found for userId: %s": "ユーザーIDに対するアプリケーションが見つかりません: %s",
|
"No application is found for userId: %s": "ユーザーIDに対するアプリケーションが見つかりません: %s",
|
||||||
"No provider for category: %s is found for application: %s": "アプリケーション:%sのカテゴリ%sのプロバイダが見つかりません",
|
"No provider for category: %s is found for application: %s": "アプリケーション:%sのカテゴリ%sのプロバイダが見つかりません",
|
||||||
|
@ -92,6 +92,8 @@
|
|||||||
"the adapter: %s is not found": "the adapter: %s is not found"
|
"the adapter: %s is not found": "the adapter: %s is not found"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
"Failed to import groups": "Failed to import groups",
|
||||||
|
"Failed to import users": "Failed to import users",
|
||||||
"Missing parameter": "Missing parameter",
|
"Missing parameter": "Missing parameter",
|
||||||
"Only admin user can specify user": "Only admin user can specify user",
|
"Only admin user can specify user": "Only admin user can specify user",
|
||||||
"Please login first": "Please login first",
|
"Please login first": "Please login first",
|
||||||
@ -162,9 +164,6 @@
|
|||||||
"New password cannot contain blank space.": "New password cannot contain blank space.",
|
"New password cannot contain blank space.": "New password cannot contain blank space.",
|
||||||
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
||||||
},
|
},
|
||||||
"user_upload": {
|
|
||||||
"Failed to import users": "Failed to import users"
|
|
||||||
},
|
|
||||||
"util": {
|
"util": {
|
||||||
"No application is found for userId: %s": "No application is found for userId: %s",
|
"No application is found for userId: %s": "No application is found for userId: %s",
|
||||||
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
||||||
|
@ -92,6 +92,8 @@
|
|||||||
"the adapter: %s is not found": "the adapter: %s is not found"
|
"the adapter: %s is not found": "the adapter: %s is not found"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
"Failed to import groups": "Failed to import groups",
|
||||||
|
"Failed to import users": "사용자 가져오기를 실패했습니다",
|
||||||
"Missing parameter": "누락된 매개변수",
|
"Missing parameter": "누락된 매개변수",
|
||||||
"Only admin user can specify user": "Only admin user can specify user",
|
"Only admin user can specify user": "Only admin user can specify user",
|
||||||
"Please login first": "먼저 로그인 하십시오",
|
"Please login first": "먼저 로그인 하십시오",
|
||||||
@ -162,9 +164,6 @@
|
|||||||
"New password cannot contain blank space.": "새 비밀번호에는 공백이 포함될 수 없습니다.",
|
"New password cannot contain blank space.": "새 비밀번호에는 공백이 포함될 수 없습니다.",
|
||||||
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
||||||
},
|
},
|
||||||
"user_upload": {
|
|
||||||
"Failed to import users": "사용자 가져오기를 실패했습니다"
|
|
||||||
},
|
|
||||||
"util": {
|
"util": {
|
||||||
"No application is found for userId: %s": "어플리케이션을 찾을 수 없습니다. userId: %s",
|
"No application is found for userId: %s": "어플리케이션을 찾을 수 없습니다. userId: %s",
|
||||||
"No provider for category: %s is found for application: %s": "어플리케이션 %s에서 %s 카테고리를 위한 공급자가 찾을 수 없습니다",
|
"No provider for category: %s is found for application: %s": "어플리케이션 %s에서 %s 카테고리를 위한 공급자가 찾을 수 없습니다",
|
||||||
|
@ -92,6 +92,8 @@
|
|||||||
"the adapter: %s is not found": "the adapter: %s is not found"
|
"the adapter: %s is not found": "the adapter: %s is not found"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
"Failed to import groups": "Failed to import groups",
|
||||||
|
"Failed to import users": "Failed to import users",
|
||||||
"Missing parameter": "Missing parameter",
|
"Missing parameter": "Missing parameter",
|
||||||
"Only admin user can specify user": "Only admin user can specify user",
|
"Only admin user can specify user": "Only admin user can specify user",
|
||||||
"Please login first": "Please login first",
|
"Please login first": "Please login first",
|
||||||
@ -162,9 +164,6 @@
|
|||||||
"New password cannot contain blank space.": "New password cannot contain blank space.",
|
"New password cannot contain blank space.": "New password cannot contain blank space.",
|
||||||
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
||||||
},
|
},
|
||||||
"user_upload": {
|
|
||||||
"Failed to import users": "Failed to import users"
|
|
||||||
},
|
|
||||||
"util": {
|
"util": {
|
||||||
"No application is found for userId: %s": "No application is found for userId: %s",
|
"No application is found for userId: %s": "No application is found for userId: %s",
|
||||||
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
||||||
|
@ -92,6 +92,8 @@
|
|||||||
"the adapter: %s is not found": "the adapter: %s is not found"
|
"the adapter: %s is not found": "the adapter: %s is not found"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
"Failed to import groups": "Failed to import groups",
|
||||||
|
"Failed to import users": "Failed to import users",
|
||||||
"Missing parameter": "Missing parameter",
|
"Missing parameter": "Missing parameter",
|
||||||
"Only admin user can specify user": "Only admin user can specify user",
|
"Only admin user can specify user": "Only admin user can specify user",
|
||||||
"Please login first": "Please login first",
|
"Please login first": "Please login first",
|
||||||
@ -162,9 +164,6 @@
|
|||||||
"New password cannot contain blank space.": "New password cannot contain blank space.",
|
"New password cannot contain blank space.": "New password cannot contain blank space.",
|
||||||
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
||||||
},
|
},
|
||||||
"user_upload": {
|
|
||||||
"Failed to import users": "Failed to import users"
|
|
||||||
},
|
|
||||||
"util": {
|
"util": {
|
||||||
"No application is found for userId: %s": "No application is found for userId: %s",
|
"No application is found for userId: %s": "No application is found for userId: %s",
|
||||||
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
||||||
|
@ -92,6 +92,8 @@
|
|||||||
"the adapter: %s is not found": "the adapter: %s is not found"
|
"the adapter: %s is not found": "the adapter: %s is not found"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
"Failed to import groups": "Failed to import groups",
|
||||||
|
"Failed to import users": "Failed to import users",
|
||||||
"Missing parameter": "Missing parameter",
|
"Missing parameter": "Missing parameter",
|
||||||
"Only admin user can specify user": "Only admin user can specify user",
|
"Only admin user can specify user": "Only admin user can specify user",
|
||||||
"Please login first": "Please login first",
|
"Please login first": "Please login first",
|
||||||
@ -162,9 +164,6 @@
|
|||||||
"New password cannot contain blank space.": "New password cannot contain blank space.",
|
"New password cannot contain blank space.": "New password cannot contain blank space.",
|
||||||
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
||||||
},
|
},
|
||||||
"user_upload": {
|
|
||||||
"Failed to import users": "Failed to import users"
|
|
||||||
},
|
|
||||||
"util": {
|
"util": {
|
||||||
"No application is found for userId: %s": "No application is found for userId: %s",
|
"No application is found for userId: %s": "No application is found for userId: %s",
|
||||||
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
||||||
|
@ -92,6 +92,8 @@
|
|||||||
"the adapter: %s is not found": "the adapter: %s is not found"
|
"the adapter: %s is not found": "the adapter: %s is not found"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
"Failed to import groups": "Failed to import groups",
|
||||||
|
"Failed to import users": "Falha ao importar usuários",
|
||||||
"Missing parameter": "Missing parameter",
|
"Missing parameter": "Missing parameter",
|
||||||
"Only admin user can specify user": "Only admin user can specify user",
|
"Only admin user can specify user": "Only admin user can specify user",
|
||||||
"Please login first": "Please login first",
|
"Please login first": "Please login first",
|
||||||
@ -162,9 +164,6 @@
|
|||||||
"New password cannot contain blank space.": "New password cannot contain blank space.",
|
"New password cannot contain blank space.": "New password cannot contain blank space.",
|
||||||
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
||||||
},
|
},
|
||||||
"user_upload": {
|
|
||||||
"Failed to import users": "Falha ao importar usuários"
|
|
||||||
},
|
|
||||||
"util": {
|
"util": {
|
||||||
"No application is found for userId: %s": "No application is found for userId: %s",
|
"No application is found for userId: %s": "No application is found for userId: %s",
|
||||||
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
||||||
|
@ -92,6 +92,8 @@
|
|||||||
"the adapter: %s is not found": "the adapter: %s is not found"
|
"the adapter: %s is not found": "the adapter: %s is not found"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
"Failed to import groups": "Failed to import groups",
|
||||||
|
"Failed to import users": "Не удалось импортировать пользователей",
|
||||||
"Missing parameter": "Отсутствующий параметр",
|
"Missing parameter": "Отсутствующий параметр",
|
||||||
"Only admin user can specify user": "Only admin user can specify user",
|
"Only admin user can specify user": "Only admin user can specify user",
|
||||||
"Please login first": "Пожалуйста, сначала войдите в систему",
|
"Please login first": "Пожалуйста, сначала войдите в систему",
|
||||||
@ -162,9 +164,6 @@
|
|||||||
"New password cannot contain blank space.": "Новый пароль не может содержать пробелы.",
|
"New password cannot contain blank space.": "Новый пароль не может содержать пробелы.",
|
||||||
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
||||||
},
|
},
|
||||||
"user_upload": {
|
|
||||||
"Failed to import users": "Не удалось импортировать пользователей"
|
|
||||||
},
|
|
||||||
"util": {
|
"util": {
|
||||||
"No application is found for userId: %s": "Не найдено заявки для пользователя с идентификатором: %s",
|
"No application is found for userId: %s": "Не найдено заявки для пользователя с идентификатором: %s",
|
||||||
"No provider for category: %s is found for application: %s": "Нет провайдера для категории: %s для приложения: %s",
|
"No provider for category: %s is found for application: %s": "Нет провайдера для категории: %s для приложения: %s",
|
||||||
|
@ -92,6 +92,8 @@
|
|||||||
"the adapter: %s is not found": "the adapter: %s is not found"
|
"the adapter: %s is not found": "the adapter: %s is not found"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
"Failed to import groups": "Failed to import groups",
|
||||||
|
"Failed to import users": "Nepodarilo sa importovať používateľov",
|
||||||
"Missing parameter": "Chýbajúci parameter",
|
"Missing parameter": "Chýbajúci parameter",
|
||||||
"Only admin user can specify user": "Only admin user can specify user",
|
"Only admin user can specify user": "Only admin user can specify user",
|
||||||
"Please login first": "Najskôr sa prosím prihláste",
|
"Please login first": "Najskôr sa prosím prihláste",
|
||||||
@ -162,9 +164,6 @@
|
|||||||
"New password cannot contain blank space.": "Nové heslo nemôže obsahovať medzery.",
|
"New password cannot contain blank space.": "Nové heslo nemôže obsahovať medzery.",
|
||||||
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
||||||
},
|
},
|
||||||
"user_upload": {
|
|
||||||
"Failed to import users": "Nepodarilo sa importovať používateľov"
|
|
||||||
},
|
|
||||||
"util": {
|
"util": {
|
||||||
"No application is found for userId: %s": "Nebola nájdená žiadna aplikácia pre userId: %s",
|
"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",
|
"No provider for category: %s is found for application: %s": "Pre aplikáciu: %s nebol nájdený žiadny poskytovateľ pre kategóriu: %s",
|
||||||
|
@ -92,6 +92,8 @@
|
|||||||
"the adapter: %s is not found": "the adapter: %s is not found"
|
"the adapter: %s is not found": "the adapter: %s is not found"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
"Failed to import groups": "Failed to import groups",
|
||||||
|
"Failed to import users": "Failed to import users",
|
||||||
"Missing parameter": "Missing parameter",
|
"Missing parameter": "Missing parameter",
|
||||||
"Only admin user can specify user": "Only admin user can specify user",
|
"Only admin user can specify user": "Only admin user can specify user",
|
||||||
"Please login first": "Please login first",
|
"Please login first": "Please login first",
|
||||||
@ -162,9 +164,6 @@
|
|||||||
"New password cannot contain blank space.": "New password cannot contain blank space.",
|
"New password cannot contain blank space.": "New password cannot contain blank space.",
|
||||||
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
||||||
},
|
},
|
||||||
"user_upload": {
|
|
||||||
"Failed to import users": "Failed to import users"
|
|
||||||
},
|
|
||||||
"util": {
|
"util": {
|
||||||
"No application is found for userId: %s": "No application is found for userId: %s",
|
"No application is found for userId: %s": "No application is found for userId: %s",
|
||||||
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
||||||
|
@ -92,6 +92,8 @@
|
|||||||
"the adapter: %s is not found": "the adapter: %s is not found"
|
"the adapter: %s is not found": "the adapter: %s is not found"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
"Failed to import groups": "Failed to import groups",
|
||||||
|
"Failed to import users": "Failed to import users",
|
||||||
"Missing parameter": "Missing parameter",
|
"Missing parameter": "Missing parameter",
|
||||||
"Only admin user can specify user": "Only admin user can specify user",
|
"Only admin user can specify user": "Only admin user can specify user",
|
||||||
"Please login first": "Please login first",
|
"Please login first": "Please login first",
|
||||||
@ -162,9 +164,6 @@
|
|||||||
"New password cannot contain blank space.": "Yeni şifreniz boşluk karakteri içeremez.",
|
"New password cannot contain blank space.": "Yeni şifreniz boşluk karakteri içeremez.",
|
||||||
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
||||||
},
|
},
|
||||||
"user_upload": {
|
|
||||||
"Failed to import users": "Failed to import users"
|
|
||||||
},
|
|
||||||
"util": {
|
"util": {
|
||||||
"No application is found for userId: %s": "No application is found for userId: %s",
|
"No application is found for userId: %s": "No application is found for userId: %s",
|
||||||
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
||||||
|
@ -92,6 +92,8 @@
|
|||||||
"the adapter: %s is not found": "the adapter: %s is not found"
|
"the adapter: %s is not found": "the adapter: %s is not found"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
"Failed to import groups": "Failed to import groups",
|
||||||
|
"Failed to import users": "Failed to import users",
|
||||||
"Missing parameter": "Missing parameter",
|
"Missing parameter": "Missing parameter",
|
||||||
"Only admin user can specify user": "Only admin user can specify user",
|
"Only admin user can specify user": "Only admin user can specify user",
|
||||||
"Please login first": "Please login first",
|
"Please login first": "Please login first",
|
||||||
@ -162,9 +164,6 @@
|
|||||||
"New password cannot contain blank space.": "New password cannot contain blank space.",
|
"New password cannot contain blank space.": "New password cannot contain blank space.",
|
||||||
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
||||||
},
|
},
|
||||||
"user_upload": {
|
|
||||||
"Failed to import users": "Failed to import users"
|
|
||||||
},
|
|
||||||
"util": {
|
"util": {
|
||||||
"No application is found for userId: %s": "No application is found for userId: %s",
|
"No application is found for userId: %s": "No application is found for userId: %s",
|
||||||
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
||||||
|
@ -92,6 +92,8 @@
|
|||||||
"the adapter: %s is not found": "the adapter: %s is not found"
|
"the adapter: %s is not found": "the adapter: %s is not found"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
"Failed to import groups": "Failed to import groups",
|
||||||
|
"Failed to import users": "Không thể nhập người dùng",
|
||||||
"Missing parameter": "Thiếu tham số",
|
"Missing parameter": "Thiếu tham số",
|
||||||
"Only admin user can specify user": "Only admin user can specify user",
|
"Only admin user can specify user": "Only admin user can specify user",
|
||||||
"Please login first": "Vui lòng đăng nhập trước",
|
"Please login first": "Vui lòng đăng nhập trước",
|
||||||
@ -162,9 +164,6 @@
|
|||||||
"New password cannot contain blank space.": "Mật khẩu mới không thể chứa dấu trắng.",
|
"New password cannot contain blank space.": "Mật khẩu mới không thể chứa dấu trắng.",
|
||||||
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
|
||||||
},
|
},
|
||||||
"user_upload": {
|
|
||||||
"Failed to import users": "Không thể nhập người dùng"
|
|
||||||
},
|
|
||||||
"util": {
|
"util": {
|
||||||
"No application is found for userId: %s": "Không tìm thấy ứng dụng cho ID người dùng: %s",
|
"No application is found for userId: %s": "Không tìm thấy ứng dụng cho ID người dùng: %s",
|
||||||
"No provider for category: %s is found for application: %s": "Không tìm thấy nhà cung cấp cho danh mục: %s cho ứng dụng: %s",
|
"No provider for category: %s is found for application: %s": "Không tìm thấy nhà cung cấp cho danh mục: %s cho ứng dụng: %s",
|
||||||
|
@ -92,6 +92,8 @@
|
|||||||
"the adapter: %s is not found": "适配器: %s 未找到"
|
"the adapter: %s is not found": "适配器: %s 未找到"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
"Failed to import groups": "导入群组失败",
|
||||||
|
"Failed to import users": "导入用户失败",
|
||||||
"Missing parameter": "缺少参数",
|
"Missing parameter": "缺少参数",
|
||||||
"Only admin user can specify user": "仅管理员用户可以指定用户",
|
"Only admin user can specify user": "仅管理员用户可以指定用户",
|
||||||
"Please login first": "请先登录",
|
"Please login first": "请先登录",
|
||||||
@ -162,9 +164,6 @@
|
|||||||
"New password cannot contain blank space.": "新密码不可以包含空格",
|
"New password cannot contain blank space.": "新密码不可以包含空格",
|
||||||
"the user's owner and name should not be empty": "用户的组织和名称不能为空"
|
"the user's owner and name should not be empty": "用户的组织和名称不能为空"
|
||||||
},
|
},
|
||||||
"user_upload": {
|
|
||||||
"Failed to import users": "导入用户失败"
|
|
||||||
},
|
|
||||||
"util": {
|
"util": {
|
||||||
"No application is found for userId: %s": "未找到用户: %s的应用",
|
"No application is found for userId: %s": "未找到用户: %s的应用",
|
||||||
"No provider for category: %s is found for application: %s": "未找到类别为: %s的提供商来满足应用: %s",
|
"No provider for category: %s is found for application: %s": "未找到类别为: %s的提供商来满足应用: %s",
|
||||||
|
@ -190,7 +190,7 @@ func (idp *DouyinIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
|
|||||||
|
|
||||||
userInfo := UserInfo{
|
userInfo := UserInfo{
|
||||||
Id: douyinUserInfo.Data.OpenId,
|
Id: douyinUserInfo.Data.OpenId,
|
||||||
Username: douyinUserInfo.Data.Nickname,
|
Username: douyinUserInfo.Data.OpenId,
|
||||||
DisplayName: douyinUserInfo.Data.Nickname,
|
DisplayName: douyinUserInfo.Data.Nickname,
|
||||||
AvatarUrl: douyinUserInfo.Data.Avatar,
|
AvatarUrl: douyinUserInfo.Data.Avatar,
|
||||||
}
|
}
|
||||||
|
2
main.go
2
main.go
@ -45,6 +45,7 @@ func main() {
|
|||||||
object.InitUserManager()
|
object.InitUserManager()
|
||||||
object.InitFromFile()
|
object.InitFromFile()
|
||||||
object.InitCasvisorConfig()
|
object.InitCasvisorConfig()
|
||||||
|
object.InitCleanupTokens()
|
||||||
|
|
||||||
util.SafeGoroutine(func() { object.RunSyncUsersJob() })
|
util.SafeGoroutine(func() { object.RunSyncUsersJob() })
|
||||||
util.SafeGoroutine(func() { controllers.InitCLIDownloader() })
|
util.SafeGoroutine(func() { controllers.InitCLIDownloader() })
|
||||||
@ -63,6 +64,7 @@ func main() {
|
|||||||
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)
|
||||||
|
beego.InsertFilter("*", beego.BeforeRouter, routers.FieldValidationFilter)
|
||||||
beego.InsertFilter("*", beego.AfterExec, routers.AfterRecordMessage, false)
|
beego.InsertFilter("*", beego.AfterExec, routers.AfterRecordMessage, false)
|
||||||
|
|
||||||
beego.BConfig.WebConfig.Session.SessionOn = true
|
beego.BConfig.WebConfig.Session.SessionOn = true
|
||||||
|
@ -252,12 +252,12 @@ func CheckPassword(user *User, password string, lang string, options ...bool) er
|
|||||||
credManager := cred.GetCredManager(passwordType)
|
credManager := cred.GetCredManager(passwordType)
|
||||||
if credManager != nil {
|
if credManager != nil {
|
||||||
if organization.MasterPassword != "" {
|
if organization.MasterPassword != "" {
|
||||||
if password == organization.MasterPassword || credManager.IsPasswordCorrect(password, organization.MasterPassword, "", organization.PasswordSalt) {
|
if password == organization.MasterPassword || credManager.IsPasswordCorrect(password, organization.MasterPassword, organization.PasswordSalt) {
|
||||||
return resetUserSigninErrorTimes(user)
|
return resetUserSigninErrorTimes(user)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if credManager.IsPasswordCorrect(password, user.Password, user.PasswordSalt, organization.PasswordSalt) {
|
if credManager.IsPasswordCorrect(password, user.Password, organization.PasswordSalt) || credManager.IsPasswordCorrect(password, user.Password, user.PasswordSalt) {
|
||||||
return resetUserSigninErrorTimes(user)
|
return resetUserSigninErrorTimes(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -593,31 +593,41 @@ func CheckUpdateUser(oldUser, user *User, lang string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckToEnableCaptcha(application *Application, organization, username string) (bool, error) {
|
func CheckToEnableCaptcha(application *Application, organization, username string, clientIp string) (bool, error) {
|
||||||
if len(application.Providers) == 0 {
|
if len(application.Providers) == 0 {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, providerItem := range application.Providers {
|
for _, providerItem := range application.Providers {
|
||||||
if providerItem.Provider == nil {
|
if providerItem.Provider == nil || providerItem.Provider.Category != "Captcha" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if providerItem.Provider.Category == "Captcha" {
|
|
||||||
if providerItem.Rule == "Dynamic" {
|
if providerItem.Rule == "Internet-Only" {
|
||||||
user, err := GetUserByFields(organization, username)
|
if util.IsInternetIp(clientIp) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if providerItem.Rule == "Dynamic" {
|
||||||
|
user, err := GetUserByFields(organization, username)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if user != nil {
|
||||||
|
failedSigninLimit, _, err := GetFailedSigninConfigByUser(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
failedSigninLimit := application.FailedSigninLimit
|
return user.SigninWrongTimes >= failedSigninLimit, nil
|
||||||
if failedSigninLimit == 0 {
|
|
||||||
failedSigninLimit = DefaultFailedSigninLimit
|
|
||||||
}
|
|
||||||
|
|
||||||
return user != nil && user.SigninWrongTimes >= failedSigninLimit, nil
|
|
||||||
}
|
}
|
||||||
return providerItem.Rule == "Always", nil
|
|
||||||
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return providerItem.Rule == "Always", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, nil
|
return false, nil
|
||||||
|
@ -181,6 +181,41 @@ func AddGroups(groups []*Group) (bool, error) {
|
|||||||
return affected != 0, nil
|
return affected != 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func AddGroupsInBatch(groups []*Group) (bool, error) {
|
||||||
|
if len(groups) == 0 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
session := ormer.Engine.NewSession()
|
||||||
|
defer session.Close()
|
||||||
|
err := session.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, group := range groups {
|
||||||
|
err = checkGroupName(group.Name)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
affected, err := session.Insert(group)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if affected == 0 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = session.Commit()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
func deleteGroup(group *Group) (bool, error) {
|
func deleteGroup(group *Group) (bool, error) {
|
||||||
affected, err := ormer.Engine.ID(core.PK{group.Owner, group.Name}).Delete(&Group{})
|
affected, err := ormer.Engine.ID(core.PK{group.Owner, group.Name}).Delete(&Group{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
61
object/group_upload.go
Normal file
61
object/group_upload.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// Copyright 2025 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/casdoor/casdoor/xlsx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getGroupMap(owner string) (map[string]*Group, error) {
|
||||||
|
m := map[string]*Group{}
|
||||||
|
|
||||||
|
groups, err := GetGroups(owner)
|
||||||
|
if err != nil {
|
||||||
|
return m, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, group := range groups {
|
||||||
|
m[group.GetId()] = group
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func UploadGroups(owner string, path string) (bool, error) {
|
||||||
|
table := xlsx.ReadXlsxFile(path)
|
||||||
|
|
||||||
|
oldGroupMap, err := getGroupMap(owner)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
transGroups, err := StringArrayToStruct[Group](table)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newGroups := []*Group{}
|
||||||
|
for _, group := range transGroups {
|
||||||
|
if _, ok := oldGroupMap[group.GetId()]; !ok {
|
||||||
|
newGroups = append(newGroups, group)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(newGroups) == 0 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return AddGroupsInBatch(newGroups)
|
||||||
|
}
|
@ -20,6 +20,7 @@ package object
|
|||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
func TestDumpToFile(t *testing.T) {
|
func TestDumpToFile(t *testing.T) {
|
||||||
|
createDatabase = false
|
||||||
InitConfig()
|
InitConfig()
|
||||||
|
|
||||||
err := DumpToFile("./init_data_dump.json")
|
err := DumpToFile("./init_data_dump.json")
|
||||||
|
@ -260,15 +260,15 @@ func AutoAdjustLdapUser(users []LdapUser) []LdapUser {
|
|||||||
res := make([]LdapUser, len(users))
|
res := make([]LdapUser, len(users))
|
||||||
for i, user := range users {
|
for i, user := range users {
|
||||||
res[i] = LdapUser{
|
res[i] = LdapUser{
|
||||||
UidNumber: user.UidNumber,
|
UidNumber: user.UidNumber,
|
||||||
Uid: user.Uid,
|
Uid: user.Uid,
|
||||||
Cn: user.Cn,
|
Cn: user.Cn,
|
||||||
GroupId: user.GidNumber,
|
GroupId: user.GidNumber,
|
||||||
Uuid: user.GetLdapUuid(),
|
Uuid: user.GetLdapUuid(),
|
||||||
DisplayName: user.DisplayName,
|
DisplayName: user.DisplayName,
|
||||||
Email: util.ReturnAnyNotEmpty(user.Email, user.EmailAddress, user.Mail),
|
Email: util.ReturnAnyNotEmpty(user.Email, user.EmailAddress, user.Mail),
|
||||||
Mobile: util.ReturnAnyNotEmpty(user.Mobile, user.MobileTelephoneNumber, user.TelephoneNumber),
|
Mobile: util.ReturnAnyNotEmpty(user.Mobile, user.MobileTelephoneNumber, user.TelephoneNumber),
|
||||||
RegisteredAddress: util.ReturnAnyNotEmpty(user.PostalAddress, user.RegisteredAddress),
|
Address: util.ReturnAnyNotEmpty(user.Address, user.PostalAddress, user.RegisteredAddress),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
|
@ -222,7 +222,7 @@ func UpdateOrganization(id string, organization *Organization, isGlobalAdmin boo
|
|||||||
if organization.MasterPassword != "" && organization.MasterPassword != "***" {
|
if organization.MasterPassword != "" && organization.MasterPassword != "***" {
|
||||||
credManager := cred.GetCredManager(organization.PasswordType)
|
credManager := cred.GetCredManager(organization.PasswordType)
|
||||||
if credManager != nil {
|
if credManager != nil {
|
||||||
hashedPassword := credManager.GetHashedPassword(organization.MasterPassword, "", organization.PasswordSalt)
|
hashedPassword := credManager.GetHashedPassword(organization.MasterPassword, organization.PasswordSalt)
|
||||||
organization.MasterPassword = hashedPassword
|
organization.MasterPassword = hashedPassword
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -536,7 +536,13 @@ func IsNeedPromptMfa(org *Organization, user *User) bool {
|
|||||||
if org == nil || user == nil {
|
if org == nil || user == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for _, item := range org.MfaItems {
|
|
||||||
|
mfaItems := org.MfaItems
|
||||||
|
|
||||||
|
if len(user.MfaItems) > 0 {
|
||||||
|
mfaItems = user.MfaItems
|
||||||
|
}
|
||||||
|
for _, item := range mfaItems {
|
||||||
if item.Rule == "Required" {
|
if item.Rule == "Required" {
|
||||||
if item.Name == EmailType && !user.MfaEmailEnabled {
|
if item.Name == EmailType && !user.MfaEmailEnabled {
|
||||||
return true
|
return true
|
||||||
|
@ -42,6 +42,7 @@ type Product struct {
|
|||||||
IsRecharge bool `json:"isRecharge"`
|
IsRecharge bool `json:"isRecharge"`
|
||||||
Providers []string `xorm:"varchar(255)" json:"providers"`
|
Providers []string `xorm:"varchar(255)" json:"providers"`
|
||||||
ReturnUrl string `xorm:"varchar(1000)" json:"returnUrl"`
|
ReturnUrl string `xorm:"varchar(1000)" json:"returnUrl"`
|
||||||
|
SuccessUrl string `xorm:"varchar(1000)" json:"successUrl"`
|
||||||
|
|
||||||
State string `xorm:"varchar(100)" json:"state"`
|
State string `xorm:"varchar(100)" json:"state"`
|
||||||
|
|
||||||
@ -213,6 +214,10 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
|
|||||||
returnUrl = fmt.Sprintf("%s/buy-plan/%s/%s/result?subscription=%s", originFrontend, owner, pricingName, sub.Name)
|
returnUrl = fmt.Sprintf("%s/buy-plan/%s/%s/result?subscription=%s", originFrontend, owner, pricingName, sub.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if product.SuccessUrl != "" {
|
||||||
|
returnUrl = fmt.Sprintf("%s?transactionOwner=%s&transactionName=%s", product.SuccessUrl, owner, paymentName)
|
||||||
|
}
|
||||||
// Create an order
|
// Create an order
|
||||||
payReq := &pp.PayReq{
|
payReq := &pp.PayReq{
|
||||||
ProviderName: providerName,
|
ProviderName: providerName,
|
||||||
|
93
object/token_cleanup.go
Normal file
93
object/token_cleanup.go
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
// Copyright 2025 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
"github.com/robfig/cron/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CleanupTokens(tokenRetentionIntervalAfterExpiry int) error {
|
||||||
|
var sessions []*Token
|
||||||
|
err := ormer.Engine.Find(&sessions)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to query expired tokens: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
currentTime := time.Now()
|
||||||
|
deletedCount := 0
|
||||||
|
|
||||||
|
for _, session := range sessions {
|
||||||
|
tokenString := session.AccessToken
|
||||||
|
token, _, err := new(jwt.Parser).ParseUnverified(tokenString, jwt.MapClaims{})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to parse token %s: %v\n", session.Name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if claims, ok := token.Claims.(jwt.MapClaims); ok {
|
||||||
|
exp, ok := claims["exp"].(float64)
|
||||||
|
if !ok {
|
||||||
|
fmt.Printf("Token %s does not have an 'exp' claim\n", session.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
expireTime := time.Unix(int64(exp), 0)
|
||||||
|
tokenAfterExpiry := currentTime.Sub(expireTime).Seconds()
|
||||||
|
if tokenAfterExpiry > float64(tokenRetentionIntervalAfterExpiry) {
|
||||||
|
_, err = ormer.Engine.Delete(session)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to delete expired token %s: %w", session.Name, err)
|
||||||
|
}
|
||||||
|
fmt.Printf("[%d] Deleted expired token: %s | Created: %s | Org: %s | App: %s | User: %s\n",
|
||||||
|
deletedCount, session.Name, session.CreatedTime, session.Organization, session.Application, session.User)
|
||||||
|
deletedCount++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Token %s is not valid\n", session.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTokenRetentionInterval(days int) int {
|
||||||
|
if days <= 0 {
|
||||||
|
days = 30
|
||||||
|
}
|
||||||
|
return days * 24 * 3600
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitCleanupTokens() {
|
||||||
|
schedule := "0 0 * * *"
|
||||||
|
interval := getTokenRetentionInterval(30)
|
||||||
|
|
||||||
|
if err := CleanupTokens(interval); err != nil {
|
||||||
|
fmt.Printf("Error cleaning up tokens at startup: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cronJob := cron.New()
|
||||||
|
_, err := cronJob.AddFunc(schedule, func() {
|
||||||
|
if err := CleanupTokens(interval); err != nil {
|
||||||
|
fmt.Printf("Error cleaning up tokens: %v\n", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error scheduling token cleanup: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cronJob.Start()
|
||||||
|
}
|
@ -212,6 +212,7 @@ type User struct {
|
|||||||
|
|
||||||
ManagedAccounts []ManagedAccount `xorm:"managedAccounts blob" json:"managedAccounts"`
|
ManagedAccounts []ManagedAccount `xorm:"managedAccounts blob" json:"managedAccounts"`
|
||||||
MfaAccounts []MfaAccount `xorm:"mfaAccounts blob" json:"mfaAccounts"`
|
MfaAccounts []MfaAccount `xorm:"mfaAccounts blob" json:"mfaAccounts"`
|
||||||
|
MfaItems []*MfaItem `xorm:"varchar(300)" json:"mfaItems"`
|
||||||
NeedUpdatePassword bool `json:"needUpdatePassword"`
|
NeedUpdatePassword bool `json:"needUpdatePassword"`
|
||||||
IpWhitelist string `xorm:"varchar(200)" json:"ipWhitelist"`
|
IpWhitelist string `xorm:"varchar(200)" json:"ipWhitelist"`
|
||||||
}
|
}
|
||||||
@ -661,6 +662,62 @@ func GetMaskedUser(user *User, isAdminOrSelf bool, errs ...error) (*User, error)
|
|||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetFilteredUser(user *User, isAdmin bool, isAdminOrSelf bool, accountItems []*AccountItem) (*User, error) {
|
||||||
|
if accountItems == nil || len(accountItems) == 0 {
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
userFieldMap := map[string]int{}
|
||||||
|
|
||||||
|
reflectedUserField := reflect.TypeOf(User{})
|
||||||
|
for i := 0; i < reflectedUserField.NumField(); i++ {
|
||||||
|
userFieldMap[strings.ToLower(reflectedUserField.Field(i).Name)] = i
|
||||||
|
}
|
||||||
|
|
||||||
|
reflectedUser := reflect.ValueOf(user).Elem()
|
||||||
|
|
||||||
|
for _, accountItem := range accountItems {
|
||||||
|
if accountItem.ViewRule == "Public" {
|
||||||
|
continue
|
||||||
|
} else if accountItem.ViewRule == "Self" && isAdminOrSelf {
|
||||||
|
continue
|
||||||
|
} else if accountItem.ViewRule == "Admin" && isAdmin {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
lowerCaseAccountItemName := strings.ToLower(accountItem.Name)
|
||||||
|
lowerCaseAccountItemName = strings.ReplaceAll(lowerCaseAccountItemName, " ", "")
|
||||||
|
|
||||||
|
switch accountItem.Name {
|
||||||
|
case "Multi-factor authentication":
|
||||||
|
lowerCaseAccountItemName = strings.ToLower("PreferredMfaType")
|
||||||
|
case "User type":
|
||||||
|
lowerCaseAccountItemName = "type"
|
||||||
|
case "Country/Region":
|
||||||
|
lowerCaseAccountItemName = "region"
|
||||||
|
case "ID card info":
|
||||||
|
{
|
||||||
|
infoKeys := []string{"idCardWithPerson", "idCardFront", "idCardWithPerson"}
|
||||||
|
for _, infoKey := range infoKeys {
|
||||||
|
if _, ok := user.Properties[infoKey]; ok {
|
||||||
|
user.Properties[infoKey] = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldIdx, ok := userFieldMap[lowerCaseAccountItemName]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
reflectedUser.Field(fieldIdx).SetZero()
|
||||||
|
}
|
||||||
|
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
|
||||||
func GetMaskedUsers(users []*User, errs ...error) ([]*User, error) {
|
func GetMaskedUsers(users []*User, errs ...error) ([]*User, error) {
|
||||||
if len(errs) > 0 && errs[0] != nil {
|
if len(errs) > 0 && errs[0] != nil {
|
||||||
return nil, errs[0]
|
return nil, errs[0]
|
||||||
@ -739,7 +796,7 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if isAdmin {
|
if isAdmin {
|
||||||
columns = append(columns, "name", "id", "email", "phone", "country_code", "type", "balance")
|
columns = append(columns, "name", "id", "email", "phone", "country_code", "type", "balance", "mfa_items")
|
||||||
}
|
}
|
||||||
|
|
||||||
columns = append(columns, "updated_time")
|
columns = append(columns, "updated_time")
|
||||||
|
@ -42,8 +42,9 @@ func (user *User) UpdateUserHash() error {
|
|||||||
func (user *User) UpdateUserPassword(organization *Organization) {
|
func (user *User) UpdateUserPassword(organization *Organization) {
|
||||||
credManager := cred.GetCredManager(organization.PasswordType)
|
credManager := cred.GetCredManager(organization.PasswordType)
|
||||||
if credManager != nil {
|
if credManager != nil {
|
||||||
hashedPassword := credManager.GetHashedPassword(user.Password, user.PasswordSalt, organization.PasswordSalt)
|
hashedPassword := credManager.GetHashedPassword(user.Password, organization.PasswordSalt)
|
||||||
user.Password = hashedPassword
|
user.Password = hashedPassword
|
||||||
user.PasswordType = organization.PasswordType
|
user.PasswordType = organization.PasswordType
|
||||||
|
user.PasswordSalt = organization.PasswordSalt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,7 @@ func UploadUsers(owner string, path string) (bool, error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
transUsers, err := StringArrayToUser(table)
|
transUsers, err := StringArrayToStruct[User](table)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
@ -263,7 +263,19 @@ func ClearUserOAuthProperties(user *User, providerType string) (bool, error) {
|
|||||||
return affected != 0, nil
|
return affected != 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang string) (bool, string) {
|
func userVisible(isAdmin bool, item *AccountItem) bool {
|
||||||
|
if item == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.ViewRule == "Admin" && !isAdmin {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, allowDisplayNameEmpty bool, lang string) (bool, string) {
|
||||||
organization, err := GetOrganizationByUser(oldUser)
|
organization, err := GetOrganizationByUser(oldUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err.Error()
|
return false, err.Error()
|
||||||
@ -273,7 +285,7 @@ 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)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.Owner = oldUser.Owner
|
newUser.Owner = oldUser.Owner
|
||||||
} else {
|
} else {
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
@ -281,7 +293,7 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
}
|
}
|
||||||
if oldUser.Name != newUser.Name {
|
if oldUser.Name != newUser.Name {
|
||||||
item := GetAccountItemByName("Name", organization)
|
item := GetAccountItemByName("Name", organization)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.Name = oldUser.Name
|
newUser.Name = oldUser.Name
|
||||||
} else {
|
} else {
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
@ -289,7 +301,7 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
}
|
}
|
||||||
if oldUser.Id != newUser.Id {
|
if oldUser.Id != newUser.Id {
|
||||||
item := GetAccountItemByName("ID", organization)
|
item := GetAccountItemByName("ID", organization)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.Id = oldUser.Id
|
newUser.Id = oldUser.Id
|
||||||
} else {
|
} else {
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
@ -297,15 +309,19 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
}
|
}
|
||||||
if oldUser.DisplayName != newUser.DisplayName {
|
if oldUser.DisplayName != newUser.DisplayName {
|
||||||
item := GetAccountItemByName("Display name", organization)
|
item := GetAccountItemByName("Display name", organization)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.DisplayName = oldUser.DisplayName
|
newUser.DisplayName = oldUser.DisplayName
|
||||||
} else {
|
} else {
|
||||||
|
if !allowDisplayNameEmpty && newUser.DisplayName == "" {
|
||||||
|
return false, i18n.Translate(lang, "user:Display name cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Avatar != newUser.Avatar {
|
if oldUser.Avatar != newUser.Avatar {
|
||||||
item := GetAccountItemByName("Avatar", organization)
|
item := GetAccountItemByName("Avatar", organization)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.Avatar = oldUser.Avatar
|
newUser.Avatar = oldUser.Avatar
|
||||||
} else {
|
} else {
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
@ -313,7 +329,7 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
}
|
}
|
||||||
if oldUser.Type != newUser.Type {
|
if oldUser.Type != newUser.Type {
|
||||||
item := GetAccountItemByName("User type", organization)
|
item := GetAccountItemByName("User type", organization)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.Type = oldUser.Type
|
newUser.Type = oldUser.Type
|
||||||
} else {
|
} else {
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
@ -322,7 +338,7 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
// 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)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.Password = oldUser.Password
|
newUser.Password = oldUser.Password
|
||||||
} else {
|
} else {
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
@ -330,7 +346,7 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
}
|
}
|
||||||
if oldUser.Email != newUser.Email {
|
if oldUser.Email != newUser.Email {
|
||||||
item := GetAccountItemByName("Email", organization)
|
item := GetAccountItemByName("Email", organization)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.Email = oldUser.Email
|
newUser.Email = oldUser.Email
|
||||||
} else {
|
} else {
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
@ -338,7 +354,7 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
}
|
}
|
||||||
if oldUser.Phone != newUser.Phone {
|
if oldUser.Phone != newUser.Phone {
|
||||||
item := GetAccountItemByName("Phone", organization)
|
item := GetAccountItemByName("Phone", organization)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.Phone = oldUser.Phone
|
newUser.Phone = oldUser.Phone
|
||||||
} else {
|
} else {
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
@ -346,7 +362,7 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
}
|
}
|
||||||
if oldUser.CountryCode != newUser.CountryCode {
|
if oldUser.CountryCode != newUser.CountryCode {
|
||||||
item := GetAccountItemByName("Country code", organization)
|
item := GetAccountItemByName("Country code", organization)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.CountryCode = oldUser.CountryCode
|
newUser.CountryCode = oldUser.CountryCode
|
||||||
} else {
|
} else {
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
@ -354,7 +370,7 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
}
|
}
|
||||||
if oldUser.Region != newUser.Region {
|
if oldUser.Region != newUser.Region {
|
||||||
item := GetAccountItemByName("Country/Region", organization)
|
item := GetAccountItemByName("Country/Region", organization)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.Region = oldUser.Region
|
newUser.Region = oldUser.Region
|
||||||
} else {
|
} else {
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
@ -362,7 +378,7 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
}
|
}
|
||||||
if oldUser.Location != newUser.Location {
|
if oldUser.Location != newUser.Location {
|
||||||
item := GetAccountItemByName("Location", organization)
|
item := GetAccountItemByName("Location", organization)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.Location = oldUser.Location
|
newUser.Location = oldUser.Location
|
||||||
} else {
|
} else {
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
@ -370,7 +386,7 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
}
|
}
|
||||||
if oldUser.Affiliation != newUser.Affiliation {
|
if oldUser.Affiliation != newUser.Affiliation {
|
||||||
item := GetAccountItemByName("Affiliation", organization)
|
item := GetAccountItemByName("Affiliation", organization)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.Affiliation = oldUser.Affiliation
|
newUser.Affiliation = oldUser.Affiliation
|
||||||
} else {
|
} else {
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
@ -378,7 +394,7 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
}
|
}
|
||||||
if oldUser.Title != newUser.Title {
|
if oldUser.Title != newUser.Title {
|
||||||
item := GetAccountItemByName("Title", organization)
|
item := GetAccountItemByName("Title", organization)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.Title = oldUser.Title
|
newUser.Title = oldUser.Title
|
||||||
} else {
|
} else {
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
@ -386,7 +402,7 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
}
|
}
|
||||||
if oldUser.Homepage != newUser.Homepage {
|
if oldUser.Homepage != newUser.Homepage {
|
||||||
item := GetAccountItemByName("Homepage", organization)
|
item := GetAccountItemByName("Homepage", organization)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.Homepage = oldUser.Homepage
|
newUser.Homepage = oldUser.Homepage
|
||||||
} else {
|
} else {
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
@ -394,7 +410,7 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
}
|
}
|
||||||
if oldUser.Bio != newUser.Bio {
|
if oldUser.Bio != newUser.Bio {
|
||||||
item := GetAccountItemByName("Bio", organization)
|
item := GetAccountItemByName("Bio", organization)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.Bio = oldUser.Bio
|
newUser.Bio = oldUser.Bio
|
||||||
} else {
|
} else {
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
@ -402,7 +418,7 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
}
|
}
|
||||||
if oldUser.Tag != newUser.Tag {
|
if oldUser.Tag != newUser.Tag {
|
||||||
item := GetAccountItemByName("Tag", organization)
|
item := GetAccountItemByName("Tag", organization)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.Tag = oldUser.Tag
|
newUser.Tag = oldUser.Tag
|
||||||
} else {
|
} else {
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
@ -410,7 +426,7 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
}
|
}
|
||||||
if oldUser.SignupApplication != newUser.SignupApplication {
|
if oldUser.SignupApplication != newUser.SignupApplication {
|
||||||
item := GetAccountItemByName("Signup application", organization)
|
item := GetAccountItemByName("Signup application", organization)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.SignupApplication = oldUser.SignupApplication
|
newUser.SignupApplication = oldUser.SignupApplication
|
||||||
} else {
|
} else {
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
@ -419,7 +435,7 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
|
|
||||||
if oldUser.Gender != newUser.Gender {
|
if oldUser.Gender != newUser.Gender {
|
||||||
item := GetAccountItemByName("Gender", organization)
|
item := GetAccountItemByName("Gender", organization)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.Gender = oldUser.Gender
|
newUser.Gender = oldUser.Gender
|
||||||
} else {
|
} else {
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
@ -428,7 +444,7 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
|
|
||||||
if oldUser.Birthday != newUser.Birthday {
|
if oldUser.Birthday != newUser.Birthday {
|
||||||
item := GetAccountItemByName("Birthday", organization)
|
item := GetAccountItemByName("Birthday", organization)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.Birthday = oldUser.Birthday
|
newUser.Birthday = oldUser.Birthday
|
||||||
} else {
|
} else {
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
@ -437,7 +453,7 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
|
|
||||||
if oldUser.Education != newUser.Education {
|
if oldUser.Education != newUser.Education {
|
||||||
item := GetAccountItemByName("Education", organization)
|
item := GetAccountItemByName("Education", organization)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.Education = oldUser.Education
|
newUser.Education = oldUser.Education
|
||||||
} else {
|
} else {
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
@ -446,7 +462,7 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
|
|
||||||
if oldUser.IdCard != newUser.IdCard {
|
if oldUser.IdCard != newUser.IdCard {
|
||||||
item := GetAccountItemByName("ID card", organization)
|
item := GetAccountItemByName("ID card", organization)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.IdCard = oldUser.IdCard
|
newUser.IdCard = oldUser.IdCard
|
||||||
} else {
|
} else {
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
@ -455,7 +471,7 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
|
|
||||||
if oldUser.IdCardType != newUser.IdCardType {
|
if oldUser.IdCardType != newUser.IdCardType {
|
||||||
item := GetAccountItemByName("ID card type", organization)
|
item := GetAccountItemByName("ID card type", organization)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.IdCardType = oldUser.IdCardType
|
newUser.IdCardType = oldUser.IdCardType
|
||||||
} else {
|
} else {
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
@ -463,10 +479,13 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
}
|
}
|
||||||
|
|
||||||
oldUserPropertiesJson, _ := json.Marshal(oldUser.Properties)
|
oldUserPropertiesJson, _ := json.Marshal(oldUser.Properties)
|
||||||
|
if newUser.Properties == nil {
|
||||||
|
newUser.Properties = make(map[string]string)
|
||||||
|
}
|
||||||
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)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.Properties = oldUser.Properties
|
newUser.Properties = oldUser.Properties
|
||||||
} else {
|
} else {
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
@ -475,7 +494,7 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
|
|
||||||
if oldUser.PreferredMfaType != newUser.PreferredMfaType {
|
if oldUser.PreferredMfaType != newUser.PreferredMfaType {
|
||||||
item := GetAccountItemByName("Multi-factor authentication", organization)
|
item := GetAccountItemByName("Multi-factor authentication", organization)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.PreferredMfaType = oldUser.PreferredMfaType
|
newUser.PreferredMfaType = oldUser.PreferredMfaType
|
||||||
} else {
|
} else {
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
@ -486,13 +505,14 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
oldUser.Groups = []string{}
|
oldUser.Groups = []string{}
|
||||||
}
|
}
|
||||||
oldUserGroupsJson, _ := json.Marshal(oldUser.Groups)
|
oldUserGroupsJson, _ := json.Marshal(oldUser.Groups)
|
||||||
|
|
||||||
if newUser.Groups == nil {
|
if newUser.Groups == nil {
|
||||||
newUser.Groups = []string{}
|
newUser.Groups = []string{}
|
||||||
}
|
}
|
||||||
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)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.Groups = oldUser.Groups
|
newUser.Groups = oldUser.Groups
|
||||||
} else {
|
} else {
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
@ -510,7 +530,7 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
newUserAddressJson, _ := json.Marshal(newUser.Address)
|
newUserAddressJson, _ := json.Marshal(newUser.Address)
|
||||||
if string(oldUserAddressJson) != string(newUserAddressJson) {
|
if string(oldUserAddressJson) != string(newUserAddressJson) {
|
||||||
item := GetAccountItemByName("Address", organization)
|
item := GetAccountItemByName("Address", organization)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.Address = oldUser.Address
|
newUser.Address = oldUser.Address
|
||||||
} else {
|
} else {
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
@ -519,7 +539,7 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
|
|
||||||
if newUser.FaceIds != nil {
|
if newUser.FaceIds != nil {
|
||||||
item := GetAccountItemByName("Face ID", organization)
|
item := GetAccountItemByName("Face ID", organization)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.FaceIds = oldUser.FaceIds
|
newUser.FaceIds = oldUser.FaceIds
|
||||||
} else {
|
} else {
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
@ -528,7 +548,7 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
|
|
||||||
if oldUser.IsAdmin != newUser.IsAdmin {
|
if oldUser.IsAdmin != newUser.IsAdmin {
|
||||||
item := GetAccountItemByName("Is admin", organization)
|
item := GetAccountItemByName("Is admin", organization)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.IsAdmin = oldUser.IsAdmin
|
newUser.IsAdmin = oldUser.IsAdmin
|
||||||
} else {
|
} else {
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
@ -537,7 +557,7 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
|
|
||||||
if oldUser.IsForbidden != newUser.IsForbidden {
|
if oldUser.IsForbidden != newUser.IsForbidden {
|
||||||
item := GetAccountItemByName("Is forbidden", organization)
|
item := GetAccountItemByName("Is forbidden", organization)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.IsForbidden = oldUser.IsForbidden
|
newUser.IsForbidden = oldUser.IsForbidden
|
||||||
} else {
|
} else {
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
@ -545,7 +565,7 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
}
|
}
|
||||||
if oldUser.IsDeleted != newUser.IsDeleted {
|
if oldUser.IsDeleted != newUser.IsDeleted {
|
||||||
item := GetAccountItemByName("Is deleted", organization)
|
item := GetAccountItemByName("Is deleted", organization)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.IsDeleted = oldUser.IsDeleted
|
newUser.IsDeleted = oldUser.IsDeleted
|
||||||
} else {
|
} else {
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
@ -553,7 +573,7 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
}
|
}
|
||||||
if oldUser.NeedUpdatePassword != newUser.NeedUpdatePassword {
|
if oldUser.NeedUpdatePassword != newUser.NeedUpdatePassword {
|
||||||
item := GetAccountItemByName("Need update password", organization)
|
item := GetAccountItemByName("Need update password", organization)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.NeedUpdatePassword = oldUser.NeedUpdatePassword
|
newUser.NeedUpdatePassword = oldUser.NeedUpdatePassword
|
||||||
} else {
|
} else {
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
@ -561,7 +581,7 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
}
|
}
|
||||||
if oldUser.IpWhitelist != newUser.IpWhitelist {
|
if oldUser.IpWhitelist != newUser.IpWhitelist {
|
||||||
item := GetAccountItemByName("IP whitelist", organization)
|
item := GetAccountItemByName("IP whitelist", organization)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.IpWhitelist = oldUser.IpWhitelist
|
newUser.IpWhitelist = oldUser.IpWhitelist
|
||||||
} else {
|
} else {
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
@ -570,7 +590,7 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
|
|
||||||
if oldUser.Balance != newUser.Balance {
|
if oldUser.Balance != newUser.Balance {
|
||||||
item := GetAccountItemByName("Balance", organization)
|
item := GetAccountItemByName("Balance", organization)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.Balance = oldUser.Balance
|
newUser.Balance = oldUser.Balance
|
||||||
} else {
|
} else {
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
@ -579,7 +599,7 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
|
|
||||||
if oldUser.Score != newUser.Score {
|
if oldUser.Score != newUser.Score {
|
||||||
item := GetAccountItemByName("Score", organization)
|
item := GetAccountItemByName("Score", organization)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.Score = oldUser.Score
|
newUser.Score = oldUser.Score
|
||||||
} else {
|
} else {
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
@ -588,7 +608,7 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
|
|
||||||
if oldUser.Karma != newUser.Karma {
|
if oldUser.Karma != newUser.Karma {
|
||||||
item := GetAccountItemByName("Karma", organization)
|
item := GetAccountItemByName("Karma", organization)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.Karma = oldUser.Karma
|
newUser.Karma = oldUser.Karma
|
||||||
} else {
|
} else {
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
@ -597,7 +617,7 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
|
|
||||||
if oldUser.Language != newUser.Language {
|
if oldUser.Language != newUser.Language {
|
||||||
item := GetAccountItemByName("Language", organization)
|
item := GetAccountItemByName("Language", organization)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.Language = oldUser.Language
|
newUser.Language = oldUser.Language
|
||||||
} else {
|
} else {
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
@ -606,7 +626,7 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
|
|
||||||
if oldUser.Ranking != newUser.Ranking {
|
if oldUser.Ranking != newUser.Ranking {
|
||||||
item := GetAccountItemByName("Ranking", organization)
|
item := GetAccountItemByName("Ranking", organization)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.Ranking = oldUser.Ranking
|
newUser.Ranking = oldUser.Ranking
|
||||||
} else {
|
} else {
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
@ -615,7 +635,7 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
|
|
||||||
if oldUser.Currency != newUser.Currency {
|
if oldUser.Currency != newUser.Currency {
|
||||||
item := GetAccountItemByName("Currency", organization)
|
item := GetAccountItemByName("Currency", organization)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.Currency = oldUser.Currency
|
newUser.Currency = oldUser.Currency
|
||||||
} else {
|
} else {
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
@ -624,7 +644,7 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
|
|
||||||
if oldUser.Hash != newUser.Hash {
|
if oldUser.Hash != newUser.Hash {
|
||||||
item := GetAccountItemByName("Hash", organization)
|
item := GetAccountItemByName("Hash", organization)
|
||||||
if item == nil {
|
if !userVisible(isAdmin, item) {
|
||||||
newUser.Hash = oldUser.Hash
|
newUser.Hash = oldUser.Hash
|
||||||
} else {
|
} else {
|
||||||
itemsChanged = append(itemsChanged, item)
|
itemsChanged = append(itemsChanged, item)
|
||||||
@ -704,14 +724,14 @@ func setReflectAttr[T any](fieldValue *reflect.Value, fieldString string) error
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func StringArrayToUser(stringArray [][]string) ([]*User, error) {
|
func StringArrayToStruct[T any](stringArray [][]string) ([]*T, error) {
|
||||||
fieldNames := stringArray[0]
|
fieldNames := stringArray[0]
|
||||||
excelMap := []map[string]string{}
|
excelMap := []map[string]string{}
|
||||||
userFieldMap := map[string]int{}
|
structFieldMap := map[string]int{}
|
||||||
|
|
||||||
reflectedUser := reflect.TypeOf(User{})
|
reflectedStruct := reflect.TypeOf(*new(T))
|
||||||
for i := 0; i < reflectedUser.NumField(); i++ {
|
for i := 0; i < reflectedStruct.NumField(); i++ {
|
||||||
userFieldMap[strings.ToLower(reflectedUser.Field(i).Name)] = i
|
structFieldMap[strings.ToLower(reflectedStruct.Field(i).Name)] = i
|
||||||
}
|
}
|
||||||
|
|
||||||
for idx, field := range stringArray {
|
for idx, field := range stringArray {
|
||||||
@ -726,22 +746,23 @@ func StringArrayToUser(stringArray [][]string) ([]*User, error) {
|
|||||||
excelMap = append(excelMap, tempMap)
|
excelMap = append(excelMap, tempMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
users := []*User{}
|
instances := []*T{}
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
for _, u := range excelMap {
|
for _, m := range excelMap {
|
||||||
user := User{}
|
instance := new(T)
|
||||||
reflectedUser := reflect.ValueOf(&user).Elem()
|
reflectedInstance := reflect.ValueOf(instance).Elem()
|
||||||
for k, v := range u {
|
|
||||||
|
for k, v := range m {
|
||||||
if v == "" || v == "null" || v == "[]" || v == "{}" {
|
if v == "" || v == "null" || v == "[]" || v == "{}" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
fName := strings.ToLower(strings.ReplaceAll(k, "_", ""))
|
fName := strings.ToLower(strings.ReplaceAll(k, "_", ""))
|
||||||
fieldIdx, ok := userFieldMap[fName]
|
fieldIdx, ok := structFieldMap[fName]
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
fv := reflectedUser.Field(fieldIdx)
|
fv := reflectedInstance.Field(fieldIdx)
|
||||||
if !fv.IsValid() {
|
if !fv.IsValid() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -786,8 +807,8 @@ func StringArrayToUser(stringArray [][]string) ([]*User, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
users = append(users, &user)
|
instances = append(instances, instance)
|
||||||
}
|
}
|
||||||
|
|
||||||
return users, nil
|
return instances, nil
|
||||||
}
|
}
|
||||||
|
@ -185,17 +185,3 @@ func removePort(s string) string {
|
|||||||
}
|
}
|
||||||
return ipStr
|
return ipStr
|
||||||
}
|
}
|
||||||
|
|
||||||
func isHostIntranet(s string) bool {
|
|
||||||
ipStr, _, err := net.SplitHostPort(s)
|
|
||||||
if err != nil {
|
|
||||||
ipStr = s
|
|
||||||
}
|
|
||||||
|
|
||||||
ip := net.ParseIP(ipStr)
|
|
||||||
if ip == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return ip.IsPrivate() || ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast()
|
|
||||||
}
|
|
||||||
|
@ -83,7 +83,7 @@ func CorsFilter(ctx *context.Context) {
|
|||||||
setCorsHeaders(ctx, origin)
|
setCorsHeaders(ctx, origin)
|
||||||
} else if originHostname == host {
|
} else if originHostname == host {
|
||||||
setCorsHeaders(ctx, origin)
|
setCorsHeaders(ctx, origin)
|
||||||
} else if isHostIntranet(host) {
|
} else if util.IsHostIntranet(host) {
|
||||||
setCorsHeaders(ctx, origin)
|
setCorsHeaders(ctx, origin)
|
||||||
} else {
|
} else {
|
||||||
ok, err := object.IsOriginAllowed(origin)
|
ok, err := object.IsOriginAllowed(origin)
|
||||||
|
56
routers/field_validation_filter.go
Normal file
56
routers/field_validation_filter.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// Copyright 2025 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package routers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/beego/beego/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
var forbiddenChars = `/?:#&%=+;`
|
||||||
|
|
||||||
|
func FieldValidationFilter(ctx *context.Context) {
|
||||||
|
if ctx.Input.Method() != "POST" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
urlPath := ctx.Request.URL.Path
|
||||||
|
if !(strings.HasPrefix(urlPath, "/api/add-") || strings.HasPrefix(urlPath, "/api/update-")) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyBytes, err := io.ReadAll(ctx.Request.Body)
|
||||||
|
if err != nil || len(bodyBytes) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Request.Body = io.NopCloser(strings.NewReader(string(bodyBytes)))
|
||||||
|
|
||||||
|
var requestData map[string]interface{}
|
||||||
|
if err := json.Unmarshal(bodyBytes, &requestData); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if value, ok := requestData["name"].(string); ok {
|
||||||
|
if strings.ContainsAny(value, forbiddenChars) {
|
||||||
|
responseError(ctx, fmt.Sprintf("Field 'name' contains forbidden characters: %q", forbiddenChars))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -81,6 +81,7 @@ func initAPI() {
|
|||||||
beego.Router("/api/update-group", &controllers.ApiController{}, "POST:UpdateGroup")
|
beego.Router("/api/update-group", &controllers.ApiController{}, "POST:UpdateGroup")
|
||||||
beego.Router("/api/add-group", &controllers.ApiController{}, "POST:AddGroup")
|
beego.Router("/api/add-group", &controllers.ApiController{}, "POST:AddGroup")
|
||||||
beego.Router("/api/delete-group", &controllers.ApiController{}, "POST:DeleteGroup")
|
beego.Router("/api/delete-group", &controllers.ApiController{}, "POST:DeleteGroup")
|
||||||
|
beego.Router("/api/upload-groups", &controllers.ApiController{}, "POST:UploadGroups")
|
||||||
|
|
||||||
beego.Router("/api/get-global-users", &controllers.ApiController{}, "GET:GetGlobalUsers")
|
beego.Router("/api/get-global-users", &controllers.ApiController{}, "GET:GetGlobalUsers")
|
||||||
beego.Router("/api/get-users", &controllers.ApiController{}, "GET:GetUsers")
|
beego.Router("/api/get-users", &controllers.ApiController{}, "GET:GetUsers")
|
||||||
|
47
util/network.go
Normal file
47
util/network.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// Copyright 2025 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func IsInternetIp(ip string) bool {
|
||||||
|
ipStr, _, err := net.SplitHostPort(ip)
|
||||||
|
if err != nil {
|
||||||
|
ipStr = ip
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedIP := net.ParseIP(ipStr)
|
||||||
|
if parsedIP == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return !parsedIP.IsPrivate() && !parsedIP.IsLoopback() && !parsedIP.IsMulticast() && !parsedIP.IsUnspecified()
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsHostIntranet(ip string) bool {
|
||||||
|
ipStr, _, err := net.SplitHostPort(ip)
|
||||||
|
if err != nil {
|
||||||
|
ipStr = ip
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedIP := net.ParseIP(ipStr)
|
||||||
|
if parsedIP == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsedIP.IsPrivate() || parsedIP.IsLoopback() || parsedIP.IsLinkLocalUnicast() || parsedIP.IsLinkLocalMulticast()
|
||||||
|
}
|
@ -30,6 +30,7 @@ import (
|
|||||||
"github.com/go-git/go-git/v5/plumbing/object"
|
"github.com/go-git/go-git/v5/plumbing/object"
|
||||||
"github.com/shirou/gopsutil/cpu"
|
"github.com/shirou/gopsutil/cpu"
|
||||||
"github.com/shirou/gopsutil/mem"
|
"github.com/shirou/gopsutil/mem"
|
||||||
|
"github.com/shirou/gopsutil/process"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SystemInfo struct {
|
type SystemInfo struct {
|
||||||
@ -60,7 +61,17 @@ func getMemoryUsage() (uint64, uint64, error) {
|
|||||||
var m runtime.MemStats
|
var m runtime.MemStats
|
||||||
runtime.ReadMemStats(&m)
|
runtime.ReadMemStats(&m)
|
||||||
|
|
||||||
return m.Alloc, virtualMem.Total, nil
|
proc, err := process.NewProcess(int32(os.Getpid()))
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
memInfo, err := proc.MemoryInfo()
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return memInfo.RSS, virtualMem.Total, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetSystemInfo() (*SystemInfo, error) {
|
func GetSystemInfo() (*SystemInfo, error) {
|
||||||
|
@ -1,97 +1,97 @@
|
|||||||
const CracoLessPlugin = require("craco-less");
|
const CracoLessPlugin = require("craco-less");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
devServer: {
|
devServer: {
|
||||||
proxy: {
|
proxy: {
|
||||||
"/api": {
|
"/api": {
|
||||||
target: "http://localhost:8000",
|
target: "http://localhost:8000",
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
},
|
},
|
||||||
"/swagger": {
|
"/swagger": {
|
||||||
target: "http://localhost:8000",
|
target: "http://localhost:8000",
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
},
|
},
|
||||||
"/files": {
|
"/files": {
|
||||||
target: "http://localhost:8000",
|
target: "http://localhost:8000",
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
},
|
},
|
||||||
"/.well-known/openid-configuration": {
|
"/.well-known/openid-configuration": {
|
||||||
target: "http://localhost:8000",
|
target: "http://localhost:8000",
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
},
|
},
|
||||||
"/cas/serviceValidate": {
|
"/cas/serviceValidate": {
|
||||||
target: "http://localhost:8000",
|
target: "http://localhost:8000",
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
},
|
},
|
||||||
"/cas/proxyValidate": {
|
"/cas/proxyValidate": {
|
||||||
target: "http://localhost:8000",
|
target: "http://localhost:8000",
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
},
|
},
|
||||||
"/cas/proxy": {
|
"/cas/proxy": {
|
||||||
target: "http://localhost:8000",
|
target: "http://localhost:8000",
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
},
|
},
|
||||||
"/cas/validate": {
|
"/cas/validate": {
|
||||||
target: "http://localhost:8000",
|
target: "http://localhost:8000",
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
},
|
},
|
||||||
"/scim": {
|
"/scim": {
|
||||||
target: "http://localhost:8000",
|
target: "http://localhost:8000",
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
{
|
{
|
||||||
plugin: CracoLessPlugin,
|
plugin: CracoLessPlugin,
|
||||||
options: {
|
options: {
|
||||||
lessLoaderOptions: {
|
lessLoaderOptions: {
|
||||||
lessOptions: {
|
lessOptions: {
|
||||||
modifyVars: {"@primary-color": "rgb(89,54,213)", "@border-radius-base": "5px"},
|
modifyVars: {"@primary-color": "rgb(89,54,213)", "@border-radius-base": "5px"},
|
||||||
javascriptEnabled: true,
|
javascriptEnabled: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
webpack: {
|
webpack: {
|
||||||
configure: (webpackConfig, { env, paths }) => {
|
configure: (webpackConfig, { env, paths }) => {
|
||||||
paths.appBuild = path.resolve(__dirname, "build-temp");
|
paths.appBuild = path.resolve(__dirname, "build-temp");
|
||||||
webpackConfig.output.path = path.resolve(__dirname, "build-temp");
|
webpackConfig.output.path = path.resolve(__dirname, "build-temp");
|
||||||
|
|
||||||
// ignore webpack warnings by source-map-loader
|
// ignore webpack warnings by source-map-loader
|
||||||
// https://github.com/facebook/create-react-app/pull/11752#issuecomment-1345231546
|
// https://github.com/facebook/create-react-app/pull/11752#issuecomment-1345231546
|
||||||
webpackConfig.ignoreWarnings = [
|
webpackConfig.ignoreWarnings = [
|
||||||
function ignoreSourcemapsloaderWarnings(warning) {
|
function ignoreSourcemapsloaderWarnings(warning) {
|
||||||
return (
|
return (
|
||||||
warning.module &&
|
warning.module &&
|
||||||
warning.module.resource.includes("node_modules") &&
|
warning.module.resource.includes("node_modules") &&
|
||||||
warning.details &&
|
warning.details &&
|
||||||
warning.details.includes("source-map-loader")
|
warning.details.includes("source-map-loader")
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// use polyfill Buffer with Webpack 5
|
// use polyfill Buffer with Webpack 5
|
||||||
// https://viglucci.io/articles/how-to-polyfill-buffer-with-webpack-5
|
// https://viglucci.io/articles/how-to-polyfill-buffer-with-webpack-5
|
||||||
// https://craco.js.org/docs/configuration/webpack/
|
// https://craco.js.org/docs/configuration/webpack/
|
||||||
webpackConfig.resolve.fallback = {
|
webpackConfig.resolve.fallback = {
|
||||||
buffer: require.resolve("buffer/"),
|
buffer: require.resolve("buffer/"),
|
||||||
process: false,
|
process: false,
|
||||||
util: false,
|
util: false,
|
||||||
url: false,
|
url: false,
|
||||||
zlib: false,
|
zlib: false,
|
||||||
stream: false,
|
stream: false,
|
||||||
http: false,
|
http: false,
|
||||||
https: false,
|
https: false,
|
||||||
assert: false,
|
assert: false,
|
||||||
crypto: false,
|
crypto: false,
|
||||||
os: false,
|
os: false,
|
||||||
fs: false,
|
fs: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
return webpackConfig;
|
return webpackConfig;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
42
web/mv.js
42
web/mv.js
@ -1,21 +1,21 @@
|
|||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
|
||||||
const sourceDir = path.join(__dirname, "build-temp");
|
const sourceDir = path.join(__dirname, "build-temp");
|
||||||
const targetDir = path.join(__dirname, "build");
|
const targetDir = path.join(__dirname, "build");
|
||||||
|
|
||||||
if (!fs.existsSync(sourceDir)) {
|
if (!fs.existsSync(sourceDir)) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.error(`Source directory "${sourceDir}" does not exist.`);
|
console.error(`Source directory "${sourceDir}" does not exist.`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fs.existsSync(targetDir)) {
|
if (fs.existsSync(targetDir)) {
|
||||||
fs.rmSync(targetDir, {recursive: true, force: true});
|
fs.rmSync(targetDir, {recursive: true, force: true});
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(`Target directory "${targetDir}" has been deleted successfully.`);
|
console.log(`Target directory "${targetDir}" has been deleted successfully.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.renameSync(sourceDir, targetDir);
|
fs.renameSync(sourceDir, targetDir);
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(`Renamed "${sourceDir}" to "${targetDir}" successfully.`);
|
console.log(`Renamed "${sourceDir}" to "${targetDir}" successfully.`);
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Button, Card, Col, ConfigProvider, Input, InputNumber, Popover, Radio, Result, Row, Select, Space, Switch, Upload} from "antd";
|
import {Button, Card, Col, ConfigProvider, Input, InputNumber, Popover, Radio, Result, Row, Select, Space, Switch, Upload, message} from "antd";
|
||||||
import {CopyOutlined, HolderOutlined, LinkOutlined, UploadOutlined, UsergroupAddOutlined} from "@ant-design/icons";
|
import {CopyOutlined, HolderOutlined, LinkOutlined, UploadOutlined, UsergroupAddOutlined} from "@ant-design/icons";
|
||||||
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
||||||
import * as CertBackend from "./backend/CertBackend";
|
import * as CertBackend from "./backend/CertBackend";
|
||||||
@ -279,6 +279,13 @@ class ApplicationEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Input value={this.state.application.name} disabled={this.state.application.name === "app-built-in"} onChange={e => {
|
<Input value={this.state.application.name} disabled={this.state.application.name === "app-built-in"} onChange={e => {
|
||||||
|
const value = e.target.value;
|
||||||
|
if (/[/?:@#&%=+;]/.test(value)) {
|
||||||
|
const invalidChars = "/ ? : @ # & % = + ;";
|
||||||
|
const messageText = i18next.t("application:Invalid characters in application name") + ":" + " " + invalidChars;
|
||||||
|
message.error(messageText);
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.updateApplicationField("name", e.target.value);
|
this.updateApplicationField("name", e.target.value);
|
||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -1,36 +1,36 @@
|
|||||||
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
//
|
//
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
export const DefaultApplication = "app-built-in";
|
export const DefaultApplication = "app-built-in";
|
||||||
|
|
||||||
export const CasvisorUrl = "";
|
export const CasvisorUrl = "";
|
||||||
|
|
||||||
export const ShowGithubCorner = false;
|
export const ShowGithubCorner = false;
|
||||||
export const IsDemoMode = false;
|
export const IsDemoMode = false;
|
||||||
|
|
||||||
export const ForceLanguage = "";
|
export const ForceLanguage = "";
|
||||||
export const DefaultLanguage = "en";
|
export const DefaultLanguage = "en";
|
||||||
|
|
||||||
export const InitThemeAlgorithm = true;
|
export const InitThemeAlgorithm = true;
|
||||||
export const ThemeDefault = {
|
export const ThemeDefault = {
|
||||||
themeType: "default",
|
themeType: "default",
|
||||||
colorPrimary: "#5734d3",
|
colorPrimary: "#5734d3",
|
||||||
borderRadius: 6,
|
borderRadius: 6,
|
||||||
isCompact: false,
|
isCompact: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CustomFooter = null;
|
export const CustomFooter = null;
|
||||||
|
|
||||||
// Blank or null to hide Ai Assistant button
|
// Blank or null to hide Ai Assistant button
|
||||||
export const AiAssistantUrl = "https://ai.casbin.com";
|
export const AiAssistantUrl = "https://ai.casbin.com";
|
||||||
|
@ -14,7 +14,8 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Link} from "react-router-dom";
|
import {Link} from "react-router-dom";
|
||||||
import {Button, Table, Tooltip} from "antd";
|
import {Button, Table, Tooltip, Upload} from "antd";
|
||||||
|
import {UploadOutlined} from "@ant-design/icons";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import * as GroupBackend from "./backend/GroupBackend";
|
import * as GroupBackend from "./backend/GroupBackend";
|
||||||
@ -87,6 +88,42 @@ class GroupListPage extends BaseListPage {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uploadFile(info) {
|
||||||
|
const {status, response: res} = info.file;
|
||||||
|
if (status === "done") {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
Setting.showMessage("success", "Groups uploaded successfully, refreshing the page");
|
||||||
|
const {pagination} = this.state;
|
||||||
|
this.fetch({pagination});
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", `Groups failed to upload: ${res.msg}`);
|
||||||
|
}
|
||||||
|
} else if (status === "error") {
|
||||||
|
Setting.showMessage("error", "File failed to upload");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderUpload() {
|
||||||
|
const props = {
|
||||||
|
name: "file",
|
||||||
|
accept: ".xlsx",
|
||||||
|
method: "post",
|
||||||
|
action: `${Setting.ServerUrl}/api/upload-groups`,
|
||||||
|
withCredentials: true,
|
||||||
|
onChange: (info) => {
|
||||||
|
this.uploadFile(info);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Upload {...props}>
|
||||||
|
<Button icon={<UploadOutlined />} id="upload-button" type="primary" size="small">
|
||||||
|
{i18next.t("group:Upload (.xlsx)")}
|
||||||
|
</Button>
|
||||||
|
</Upload>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
renderTable(data) {
|
renderTable(data) {
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
@ -231,7 +268,10 @@ class GroupListPage extends BaseListPage {
|
|||||||
title={() => (
|
title={() => (
|
||||||
<div>
|
<div>
|
||||||
{i18next.t("general:Groups")}
|
{i18next.t("general:Groups")}
|
||||||
<Button type="primary" size="small" onClick={this.addGroup.bind(this)}>{i18next.t("general:Add")}</Button>
|
<Button style={{marginRight: "5px"}} type="primary" size="small" onClick={this.addGroup.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||||
|
{
|
||||||
|
this.renderUpload()
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
loading={this.state.loading}
|
loading={this.state.loading}
|
||||||
|
@ -288,6 +288,16 @@ class ProductEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("product:Success URL"), i18next.t("product:Success URL - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input prefix={<LinkOutlined />} value={this.state.product.successUrl} onChange={e => {
|
||||||
|
this.updateProductField("successUrl", 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:State"), i18next.t("general:State - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:State"), i18next.t("general:State - Tooltip"))} :
|
||||||
|
@ -371,11 +371,6 @@ class ProviderEditPage extends React.Component {
|
|||||||
{id: "Third-party", name: i18next.t("provider:Third-party")},
|
{id: "Third-party", name: i18next.t("provider:Third-party")},
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
} else if (type === "Aliyun Captcha") {
|
|
||||||
return [
|
|
||||||
{id: "nc", name: i18next.t("provider:Sliding Validation")},
|
|
||||||
{id: "ic", name: i18next.t("provider:Intelligent Validation")},
|
|
||||||
];
|
|
||||||
} else {
|
} else {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@ -674,7 +669,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
{
|
{
|
||||||
this.state.provider.type !== "WeCom" && this.state.provider.type !== "Infoflow" && this.state.provider.type !== "Aliyun Captcha" ? null : (
|
this.state.provider.type !== "WeCom" && this.state.provider.type !== "Infoflow" ? null : (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={2}>
|
<Col style={{marginTop: "5px"}} span={2}>
|
||||||
|
@ -1,341 +1,341 @@
|
|||||||
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
//
|
//
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Button, Image, Table, Upload} from "antd";
|
import {Button, Image, Table, Upload} from "antd";
|
||||||
import {UploadOutlined} from "@ant-design/icons";
|
import {UploadOutlined} from "@ant-design/icons";
|
||||||
import copy from "copy-to-clipboard";
|
import copy from "copy-to-clipboard";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import * as ResourceBackend from "./backend/ResourceBackend";
|
import * as ResourceBackend from "./backend/ResourceBackend";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import {Link} from "react-router-dom";
|
import {Link} from "react-router-dom";
|
||||||
import BaseListPage from "./BaseListPage";
|
import BaseListPage from "./BaseListPage";
|
||||||
import PopconfirmModal from "./common/modal/PopconfirmModal";
|
import PopconfirmModal from "./common/modal/PopconfirmModal";
|
||||||
|
|
||||||
class ResourceListPage extends BaseListPage {
|
class ResourceListPage extends BaseListPage {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.setState({
|
this.setState({
|
||||||
fileList: [],
|
fileList: [],
|
||||||
uploading: false,
|
uploading: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteResource(i) {
|
deleteResource(i) {
|
||||||
ResourceBackend.deleteResource(this.state.data[i])
|
ResourceBackend.deleteResource(this.state.data[i])
|
||||||
.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.fetch({
|
this.fetch({
|
||||||
pagination: {
|
pagination: {
|
||||||
...this.state.pagination,
|
...this.state.pagination,
|
||||||
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
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}`);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUpload(info) {
|
handleUpload(info) {
|
||||||
this.setState({uploading: true});
|
this.setState({uploading: true});
|
||||||
const filename = info.fileList[0].name;
|
const filename = info.fileList[0].name;
|
||||||
const fullFilePath = `resource/${this.props.account.owner}/${this.props.account.name}/${filename}`;
|
const fullFilePath = `resource/${this.props.account.owner}/${this.props.account.name}/${filename}`;
|
||||||
ResourceBackend.uploadResource(this.props.account.owner, this.props.account.name, "custom", "ResourceListPage", fullFilePath, info.file)
|
ResourceBackend.uploadResource(this.props.account.owner, this.props.account.name, "custom", "ResourceListPage", fullFilePath, info.file)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("application:File uploaded successfully"));
|
Setting.showMessage("success", i18next.t("application:File uploaded successfully"));
|
||||||
|
|
||||||
const {pagination} = this.state;
|
const {pagination} = this.state;
|
||||||
this.fetch({pagination});
|
this.fetch({pagination});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", res.msg);
|
Setting.showMessage("error", res.msg);
|
||||||
}
|
}
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
this.setState({uploading: false});
|
this.setState({uploading: false});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
renderUpload() {
|
renderUpload() {
|
||||||
return (
|
return (
|
||||||
<Upload maxCount={1} accept="image/*,video/*,audio/*,.pdf,.doc,.docx,.csv,.xls,.xlsx" showUploadList={false}
|
<Upload maxCount={1} accept="image/*,video/*,audio/*,.pdf,.doc,.docx,.csv,.xls,.xlsx" showUploadList={false}
|
||||||
beforeUpload={file => {return false;}} onChange={info => {this.handleUpload(info);}}>
|
beforeUpload={file => {return false;}} onChange={info => {this.handleUpload(info);}}>
|
||||||
<Button id="upload-button" icon={<UploadOutlined />} loading={this.state.uploading} type="primary" size="small">
|
<Button id="upload-button" icon={<UploadOutlined />} loading={this.state.uploading} type="primary" size="small">
|
||||||
{i18next.t("resource:Upload a file...")}
|
{i18next.t("resource:Upload a file...")}
|
||||||
</Button>
|
</Button>
|
||||||
</Upload>
|
</Upload>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderTable(resources) {
|
renderTable(resources) {
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: i18next.t("general:Provider"),
|
title: i18next.t("general:Provider"),
|
||||||
dataIndex: "provider",
|
dataIndex: "provider",
|
||||||
key: "provider",
|
key: "provider",
|
||||||
width: "150px",
|
width: "150px",
|
||||||
sorter: true,
|
sorter: true,
|
||||||
...this.getColumnSearchProps("provider"),
|
...this.getColumnSearchProps("provider"),
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return (
|
return (
|
||||||
<Link to={`/providers/${record.owner}/${text}`}>
|
<Link to={`/providers/${record.owner}/${text}`}>
|
||||||
{text}
|
{text}
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("general:Organization"),
|
title: i18next.t("general:Organization"),
|
||||||
dataIndex: "owner",
|
dataIndex: "owner",
|
||||||
key: "owner",
|
key: "owner",
|
||||||
width: "120px",
|
width: "120px",
|
||||||
sorter: true,
|
sorter: true,
|
||||||
...this.getColumnSearchProps("owner"),
|
...this.getColumnSearchProps("owner"),
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return (
|
return (
|
||||||
<Link to={`/organizations/${text}`}>
|
<Link to={`/organizations/${text}`}>
|
||||||
{text}
|
{text}
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("general:Application"),
|
title: i18next.t("general:Application"),
|
||||||
dataIndex: "application",
|
dataIndex: "application",
|
||||||
key: "application",
|
key: "application",
|
||||||
width: "80px",
|
width: "80px",
|
||||||
sorter: true,
|
sorter: true,
|
||||||
...this.getColumnSearchProps("application"),
|
...this.getColumnSearchProps("application"),
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return (
|
return (
|
||||||
<Link to={`/applications/${record.owner}/${text}`}>
|
<Link to={`/applications/${record.owner}/${text}`}>
|
||||||
{text}
|
{text}
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("general:User"),
|
title: i18next.t("general:User"),
|
||||||
dataIndex: "user",
|
dataIndex: "user",
|
||||||
key: "user",
|
key: "user",
|
||||||
width: "80px",
|
width: "80px",
|
||||||
sorter: true,
|
sorter: true,
|
||||||
...this.getColumnSearchProps("user"),
|
...this.getColumnSearchProps("user"),
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return (
|
return (
|
||||||
<Link to={`/users/${record.owner}/${record.user}`}>
|
<Link to={`/users/${record.owner}/${record.user}`}>
|
||||||
{text}
|
{text}
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("resource:Parent"),
|
title: i18next.t("resource:Parent"),
|
||||||
dataIndex: "parent",
|
dataIndex: "parent",
|
||||||
key: "parent",
|
key: "parent",
|
||||||
width: "80px",
|
width: "80px",
|
||||||
sorter: true,
|
sorter: true,
|
||||||
...this.getColumnSearchProps("parent"),
|
...this.getColumnSearchProps("parent"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("general:Name"),
|
title: i18next.t("general:Name"),
|
||||||
dataIndex: "name",
|
dataIndex: "name",
|
||||||
key: "name",
|
key: "name",
|
||||||
width: "150px",
|
width: "150px",
|
||||||
sorter: true,
|
sorter: true,
|
||||||
...this.getColumnSearchProps("name"),
|
...this.getColumnSearchProps("name"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("general:Created time"),
|
title: i18next.t("general:Created time"),
|
||||||
dataIndex: "createdTime",
|
dataIndex: "createdTime",
|
||||||
key: "createdTime",
|
key: "createdTime",
|
||||||
width: "150px",
|
width: "150px",
|
||||||
sorter: true,
|
sorter: true,
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return Setting.getFormattedDate(text);
|
return Setting.getFormattedDate(text);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("user:Tag"),
|
title: i18next.t("user:Tag"),
|
||||||
dataIndex: "tag",
|
dataIndex: "tag",
|
||||||
key: "tag",
|
key: "tag",
|
||||||
width: "80px",
|
width: "80px",
|
||||||
sorter: true,
|
sorter: true,
|
||||||
...this.getColumnSearchProps("tag"),
|
...this.getColumnSearchProps("tag"),
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// title: i18next.t("resource:File name"),
|
// title: i18next.t("resource:File name"),
|
||||||
// dataIndex: 'fileName',
|
// dataIndex: 'fileName',
|
||||||
// key: 'fileName',
|
// key: 'fileName',
|
||||||
// width: '120px',
|
// width: '120px',
|
||||||
// sorter: (a, b) => a.fileName.localeCompare(b.fileName),
|
// sorter: (a, b) => a.fileName.localeCompare(b.fileName),
|
||||||
// },
|
// },
|
||||||
{
|
{
|
||||||
title: i18next.t("provider:Type"),
|
title: i18next.t("provider:Type"),
|
||||||
dataIndex: "fileType",
|
dataIndex: "fileType",
|
||||||
key: "fileType",
|
key: "fileType",
|
||||||
width: "80px",
|
width: "80px",
|
||||||
sorter: true,
|
sorter: true,
|
||||||
...this.getColumnSearchProps("fileType"),
|
...this.getColumnSearchProps("fileType"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("resource:Format"),
|
title: i18next.t("resource:Format"),
|
||||||
dataIndex: "fileFormat",
|
dataIndex: "fileFormat",
|
||||||
key: "fileFormat",
|
key: "fileFormat",
|
||||||
width: "80px",
|
width: "80px",
|
||||||
sorter: true,
|
sorter: true,
|
||||||
...this.getColumnSearchProps("fileFormat"),
|
...this.getColumnSearchProps("fileFormat"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("resource:File size"),
|
title: i18next.t("resource:File size"),
|
||||||
dataIndex: "fileSize",
|
dataIndex: "fileSize",
|
||||||
key: "fileSize",
|
key: "fileSize",
|
||||||
width: "100px",
|
width: "100px",
|
||||||
sorter: true,
|
sorter: true,
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return Setting.getFriendlyFileSize(text);
|
return Setting.getFriendlyFileSize(text);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("general:Preview"),
|
title: i18next.t("general:Preview"),
|
||||||
dataIndex: "preview",
|
dataIndex: "preview",
|
||||||
key: "preview",
|
key: "preview",
|
||||||
width: "100px",
|
width: "100px",
|
||||||
fixed: (Setting.isMobile()) ? "false" : "right",
|
fixed: (Setting.isMobile()) ? "false" : "right",
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
if (record.fileType === "image") {
|
if (record.fileType === "image") {
|
||||||
const errorImage = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMIAAADDCAYAAADQvc6UAAABRWlDQ1BJQ0MgUHJvZmlsZQAAKJFjYGASSSwoyGFhYGDIzSspCnJ3UoiIjFJgf8LAwSDCIMogwMCcmFxc4BgQ4ANUwgCjUcG3awyMIPqyLsis7PPOq3QdDFcvjV3jOD1boQVTPQrgSkktTgbSf4A4LbmgqISBgTEFyFYuLykAsTuAbJEioKOA7DkgdjqEvQHEToKwj4DVhAQ5A9k3gGyB5IxEoBmML4BsnSQk8XQkNtReEOBxcfXxUQg1Mjc0dyHgXNJBSWpFCYh2zi+oLMpMzyhRcASGUqqCZ16yno6CkYGRAQMDKMwhqj/fAIcloxgHQqxAjIHBEugw5sUIsSQpBobtQPdLciLEVJYzMPBHMDBsayhILEqEO4DxG0txmrERhM29nYGBddr//5/DGRjYNRkY/l7////39v///y4Dmn+LgeHANwDrkl1AuO+pmgAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAwqADAAQAAAABAAAAwwAAAAD9b/HnAAAHlklEQVR4Ae3dP3PTWBSGcbGzM6GCKqlIBRV0dHRJFarQ0eUT8LH4BnRU0NHR0UEFVdIlFRV7TzRksomPY8uykTk/zewQfKw/9znv4yvJynLv4uLiV2dBoDiBf4qP3/ARuCRABEFAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghgg0Aj8i0JO4OzsrPv69Wv+hi2qPHr0qNvf39+iI97soRIh4f3z58/u7du3SXX7Xt7Z2enevHmzfQe+oSN2apSAPj09TSrb+XKI/f379+08+A0cNRE2ANkupk+ACNPvkSPcAAEibACyXUyfABGm3yNHuAECRNgAZLuYPgEirKlHu7u7XdyytGwHAd8jjNyng4OD7vnz51dbPT8/7z58+NB9+/bt6jU/TI+AGWHEnrx48eJ/EsSmHzx40L18+fLyzxF3ZVMjEyDCiEDjMYZZS5wiPXnyZFbJaxMhQIQRGzHvWR7XCyOCXsOmiDAi1HmPMMQjDpbpEiDCiL358eNHurW/5SnWdIBbXiDCiA38/Pnzrce2YyZ4//59F3ePLNMl4PbpiL2J0L979+7yDtHDhw8vtzzvdGnEXdvUigSIsCLAWavHp/+qM0BcXMd/q25n1vF57TYBp0a3mUzilePj4+7k5KSLb6gt6ydAhPUzXnoPR0dHl79WGTNCfBnn1uvSCJdegQhLI1vvCk+fPu2ePXt2tZOYEV6/fn31dz+shwAR1sP1cqvLntbEN9MxA9xcYjsxS1jWR4AIa2Ibzx0tc44fYX/16lV6NDFLXH+YL32jwiACRBiEbf5KcXoTIsQSpzXx4N28Ja4BQoK7rgXiydbHjx/P25TaQAJEGAguWy0+2Q8PD6/Ki4R8EVl+bzBOnZY95fq9rj9zAkTI2SxdidBHqG9+skdw43borCXO/ZcJdraPWdv22uIEiLA4q7nvvCug8WTqzQveOH26fodo7g6uFe/a17W3+nFBAkRYENRdb1vkkz1CH9cPsVy/jrhr27PqMYvENYNlHAIesRiBYwRy0V+8iXP8+/fvX11Mr7L7ECueb/r48eMqm7FuI2BGWDEG8cm+7G3NEOfmdcTQw4h9/55lhm7DekRYKQPZF2ArbXTAyu4kDYB2YxUzwg0gi/41ztHnfQG26HbGel/crVrm7tNY+/1btkOEAZ2M05r4FB7r9GbAIdxaZYrHdOsgJ/wCEQY0J74TmOKnbxxT9n3FgGGWWsVdowHtjt9Nnvf7yQM2aZU/TIAIAxrw6dOnAWtZZcoEnBpNuTuObWMEiLAx1HY0ZQJEmHJ3HNvGCBBhY6jtaMoEiJB0Z29vL6ls58vxPcO8/zfrdo5qvKO+d3Fx8Wu8zf1dW4p/cPzLly/dtv9Ts/EbcvGAHhHyfBIhZ6NSiIBTo0LNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiEC/wGgKKC4YMA4TAAAAABJRU5ErkJggg==";
|
const errorImage = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMIAAADDCAYAAADQvc6UAAABRWlDQ1BJQ0MgUHJvZmlsZQAAKJFjYGASSSwoyGFhYGDIzSspCnJ3UoiIjFJgf8LAwSDCIMogwMCcmFxc4BgQ4ANUwgCjUcG3awyMIPqyLsis7PPOq3QdDFcvjV3jOD1boQVTPQrgSkktTgbSf4A4LbmgqISBgTEFyFYuLykAsTuAbJEioKOA7DkgdjqEvQHEToKwj4DVhAQ5A9k3gGyB5IxEoBmML4BsnSQk8XQkNtReEOBxcfXxUQg1Mjc0dyHgXNJBSWpFCYh2zi+oLMpMzyhRcASGUqqCZ16yno6CkYGRAQMDKMwhqj/fAIcloxgHQqxAjIHBEugw5sUIsSQpBobtQPdLciLEVJYzMPBHMDBsayhILEqEO4DxG0txmrERhM29nYGBddr//5/DGRjYNRkY/l7////39v///y4Dmn+LgeHANwDrkl1AuO+pmgAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAwqADAAQAAAABAAAAwwAAAAD9b/HnAAAHlklEQVR4Ae3dP3PTWBSGcbGzM6GCKqlIBRV0dHRJFarQ0eUT8LH4BnRU0NHR0UEFVdIlFRV7TzRksomPY8uykTk/zewQfKw/9znv4yvJynLv4uLiV2dBoDiBf4qP3/ARuCRABEFAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghgg0Aj8i0JO4OzsrPv69Wv+hi2qPHr0qNvf39+iI97soRIh4f3z58/u7du3SXX7Xt7Z2enevHmzfQe+oSN2apSAPj09TSrb+XKI/f379+08+A0cNRE2ANkupk+ACNPvkSPcAAEibACyXUyfABGm3yNHuAECRNgAZLuYPgEirKlHu7u7XdyytGwHAd8jjNyng4OD7vnz51dbPT8/7z58+NB9+/bt6jU/TI+AGWHEnrx48eJ/EsSmHzx40L18+fLyzxF3ZVMjEyDCiEDjMYZZS5wiPXnyZFbJaxMhQIQRGzHvWR7XCyOCXsOmiDAi1HmPMMQjDpbpEiDCiL358eNHurW/5SnWdIBbXiDCiA38/Pnzrce2YyZ4//59F3ePLNMl4PbpiL2J0L979+7yDtHDhw8vtzzvdGnEXdvUigSIsCLAWavHp/+qM0BcXMd/q25n1vF57TYBp0a3mUzilePj4+7k5KSLb6gt6ydAhPUzXnoPR0dHl79WGTNCfBnn1uvSCJdegQhLI1vvCk+fPu2ePXt2tZOYEV6/fn31dz+shwAR1sP1cqvLntbEN9MxA9xcYjsxS1jWR4AIa2Ibzx0tc44fYX/16lV6NDFLXH+YL32jwiACRBiEbf5KcXoTIsQSpzXx4N28Ja4BQoK7rgXiydbHjx/P25TaQAJEGAguWy0+2Q8PD6/Ki4R8EVl+bzBOnZY95fq9rj9zAkTI2SxdidBHqG9+skdw43borCXO/ZcJdraPWdv22uIEiLA4q7nvvCug8WTqzQveOH26fodo7g6uFe/a17W3+nFBAkRYENRdb1vkkz1CH9cPsVy/jrhr27PqMYvENYNlHAIesRiBYwRy0V+8iXP8+/fvX11Mr7L7ECueb/r48eMqm7FuI2BGWDEG8cm+7G3NEOfmdcTQw4h9/55lhm7DekRYKQPZF2ArbXTAyu4kDYB2YxUzwg0gi/41ztHnfQG26HbGel/crVrm7tNY+/1btkOEAZ2M05r4FB7r9GbAIdxaZYrHdOsgJ/wCEQY0J74TmOKnbxxT9n3FgGGWWsVdowHtjt9Nnvf7yQM2aZU/TIAIAxrw6dOnAWtZZcoEnBpNuTuObWMEiLAx1HY0ZQJEmHJ3HNvGCBBhY6jtaMoEiJB0Z29vL6ls58vxPcO8/zfrdo5qvKO+d3Fx8Wu8zf1dW4p/cPzLly/dtv9Ts/EbcvGAHhHyfBIhZ6NSiIBTo0LNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiEC/wGgKKC4YMA4TAAAAABJRU5ErkJggg==";
|
||||||
return (
|
return (
|
||||||
<Image
|
<Image
|
||||||
width={200}
|
width={200}
|
||||||
src={record.url}
|
src={record.url}
|
||||||
fallback={errorImage}
|
fallback={errorImage}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (record.fileType === "video") {
|
} else if (record.fileType === "video") {
|
||||||
return (
|
return (
|
||||||
<video width={200} controls>
|
<video width={200} controls>
|
||||||
<source src={record.url} type="video/mp4" />
|
<source src={record.url} type="video/mp4" />
|
||||||
</video>
|
</video>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("general:URL"),
|
title: i18next.t("general:URL"),
|
||||||
dataIndex: "url",
|
dataIndex: "url",
|
||||||
key: "url",
|
key: "url",
|
||||||
width: "120px",
|
width: "120px",
|
||||||
fixed: (Setting.isMobile()) ? "false" : "right",
|
fixed: (Setting.isMobile()) ? "false" : "right",
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Button onClick={() => {
|
<Button onClick={() => {
|
||||||
copy(record.url);
|
copy(record.url);
|
||||||
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
|
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{i18next.t("resource:Copy Link")}
|
{i18next.t("resource:Copy Link")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("general:Action"),
|
title: i18next.t("general:Action"),
|
||||||
dataIndex: "",
|
dataIndex: "",
|
||||||
key: "op",
|
key: "op",
|
||||||
width: "70px",
|
width: "70px",
|
||||||
fixed: (Setting.isMobile()) ? "false" : "right",
|
fixed: (Setting.isMobile()) ? "false" : "right",
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<PopconfirmModal
|
<PopconfirmModal
|
||||||
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
||||||
onConfirm={() => this.deleteResource(index)}
|
onConfirm={() => this.deleteResource(index)}
|
||||||
okText={i18next.t("general:OK")}
|
okText={i18next.t("general:OK")}
|
||||||
cancelText={i18next.t("general:Cancel")}
|
cancelText={i18next.t("general:Cancel")}
|
||||||
>
|
>
|
||||||
</PopconfirmModal>
|
</PopconfirmModal>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const paginationProps = {
|
const paginationProps = {
|
||||||
total: this.state.pagination.total,
|
total: this.state.pagination.total,
|
||||||
showQuickJumper: true,
|
showQuickJumper: true,
|
||||||
showSizeChanger: true,
|
showSizeChanger: true,
|
||||||
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
|
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={resources} rowKey="name" size="middle" bordered pagination={paginationProps}
|
<Table scroll={{x: "max-content"}} columns={columns} dataSource={resources} rowKey="name" size="middle" bordered pagination={paginationProps}
|
||||||
title={() => (
|
title={() => (
|
||||||
<div>
|
<div>
|
||||||
{i18next.t("general:Resources")}
|
{i18next.t("general:Resources")}
|
||||||
{/* <Button type="primary" size="small" onClick={this.addResource.bind(this)}>{i18next.t("general:Add")}</Button>*/}
|
{/* <Button type="primary" size="small" onClick={this.addResource.bind(this)}>{i18next.t("general:Add")}</Button>*/}
|
||||||
{
|
{
|
||||||
this.renderUpload()
|
this.renderUpload()
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
loading={this.state.loading}
|
loading={this.state.loading}
|
||||||
onChange={this.handleTableChange}
|
onChange={this.handleTableChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch = (params = {}) => {
|
fetch = (params = {}) => {
|
||||||
const field = params.searchedColumn, value = params.searchText;
|
const field = params.searchedColumn, value = params.searchText;
|
||||||
const sortField = params.sortField, sortOrder = params.sortOrder;
|
const sortField = params.sortField, sortOrder = params.sortOrder;
|
||||||
this.setState({loading: true});
|
this.setState({loading: true});
|
||||||
ResourceBackend.getResources(Setting.isDefaultOrganizationSelected(this.props.account) ? "" : Setting.getRequestOrganization(this.props.account), this.props.account.name, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
|
ResourceBackend.getResources(Setting.isDefaultOrganizationSelected(this.props.account) ? "" : Setting.getRequestOrganization(this.props.account), this.props.account.name, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false,
|
loading: false,
|
||||||
});
|
});
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
this.setState({
|
this.setState({
|
||||||
data: res.data,
|
data: res.data,
|
||||||
pagination: {
|
pagination: {
|
||||||
...params.pagination,
|
...params.pagination,
|
||||||
total: res.data2,
|
total: res.data2,
|
||||||
},
|
},
|
||||||
searchText: params.searchText,
|
searchText: params.searchText,
|
||||||
searchedColumn: params.searchedColumn,
|
searchedColumn: params.searchedColumn,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (res.data.includes("Please login first")) {
|
if (res.data.includes("Please login first")) {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false,
|
loading: false,
|
||||||
isAuthorized: false,
|
isAuthorized: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ResourceListPage;
|
export default ResourceListPage;
|
||||||
|
@ -696,18 +696,27 @@ export const MfaRulePrompted = "Prompted";
|
|||||||
export const MfaRuleOptional = "Optional";
|
export const MfaRuleOptional = "Optional";
|
||||||
|
|
||||||
export function isRequiredEnableMfa(user, organization) {
|
export function isRequiredEnableMfa(user, organization) {
|
||||||
if (!user || !organization || !organization.mfaItems) {
|
if (!user || !organization || (!organization.mfaItems && !user.mfaItems)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return getMfaItemsByRules(user, organization, [MfaRuleRequired]).length > 0;
|
return getMfaItemsByRules(user, organization, [MfaRuleRequired]).length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMfaItemsByRules(user, organization, mfaRules = []) {
|
export function getMfaItemsByRules(user, organization, mfaRules = []) {
|
||||||
if (!user || !organization || !organization.mfaItems) {
|
if (!user || !organization || (!organization.mfaItems && !user.mfaItems)) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return organization.mfaItems.filter((mfaItem) => mfaRules.includes(mfaItem.rule))
|
let mfaItems = organization.mfaItems;
|
||||||
|
if (user.mfaItems && user.mfaItems.length !== 0) {
|
||||||
|
mfaItems = user.mfaItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mfaItems === null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return mfaItems.filter((mfaItem) => mfaRules.includes(mfaItem.rule))
|
||||||
.filter((mfaItem) => user.multiFactorAuths.some((mfa) => mfa.mfaType === mfaItem.name && !mfa.enabled));
|
.filter((mfaItem) => user.multiFactorAuths.some((mfa) => mfa.mfaType === mfaItem.name && !mfa.enabled));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,6 +42,7 @@ import * as MfaBackend from "./backend/MfaBackend";
|
|||||||
import AccountAvatar from "./account/AccountAvatar";
|
import AccountAvatar from "./account/AccountAvatar";
|
||||||
import FaceIdTable from "./table/FaceIdTable";
|
import FaceIdTable from "./table/FaceIdTable";
|
||||||
import MfaAccountTable from "./table/MfaAccountTable";
|
import MfaAccountTable from "./table/MfaAccountTable";
|
||||||
|
import MfaTable from "./table/MfaTable";
|
||||||
|
|
||||||
const {Option} = Select;
|
const {Option} = Select;
|
||||||
|
|
||||||
@ -926,6 +927,19 @@ class UserEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
);
|
);
|
||||||
|
} else if (accountItem.name === "MFA items") {
|
||||||
|
return (<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:MFA items"), i18next.t("general:MFA items - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<MfaTable
|
||||||
|
title={i18next.t("general:MFA items")}
|
||||||
|
table={this.state.user.mfaItems ?? []}
|
||||||
|
onUpdateTable={(value) => {this.updateUserField("mfaItems", value);}}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>);
|
||||||
} else if (accountItem.name === "Multi-factor authentication") {
|
} else if (accountItem.name === "Multi-factor authentication") {
|
||||||
return (
|
return (
|
||||||
!this.isSelfOrAdmin() ? null : (
|
!this.isSelfOrAdmin() ? null : (
|
||||||
|
@ -163,7 +163,7 @@ export function getWechatQRCode(providerId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getCaptchaStatus(values) {
|
export function getCaptchaStatus(values) {
|
||||||
return fetch(`${Setting.ServerUrl}/api/get-captcha-status?organization=${values["organization"]}&userId=${values["username"]}`, {
|
return fetch(`${Setting.ServerUrl}/api/get-captcha-status?organization=${values["organization"]}&userId=${values["username"]}&application=${values["application"]}`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -166,7 +166,7 @@ class AuthCallback extends React.Component {
|
|||||||
const responseType = this.getResponseType();
|
const responseType = this.getResponseType();
|
||||||
const handleLogin = (res) => {
|
const handleLogin = (res) => {
|
||||||
if (responseType === "login") {
|
if (responseType === "login") {
|
||||||
if (res.data2) {
|
if (res.data3) {
|
||||||
sessionStorage.setItem("signinUrl", signinUrl);
|
sessionStorage.setItem("signinUrl", signinUrl);
|
||||||
Setting.goToLinkSoft(this, `/forget/${applicationName}`);
|
Setting.goToLinkSoft(this, `/forget/${applicationName}`);
|
||||||
return;
|
return;
|
||||||
@ -176,7 +176,7 @@ class AuthCallback extends React.Component {
|
|||||||
const link = Setting.getFromLink();
|
const link = Setting.getFromLink();
|
||||||
Setting.goToLink(link);
|
Setting.goToLink(link);
|
||||||
} else if (responseType === "code") {
|
} else if (responseType === "code") {
|
||||||
if (res.data2) {
|
if (res.data3) {
|
||||||
sessionStorage.setItem("signinUrl", signinUrl);
|
sessionStorage.setItem("signinUrl", signinUrl);
|
||||||
Setting.goToLinkSoft(this, `/forget/${applicationName}`);
|
Setting.goToLinkSoft(this, `/forget/${applicationName}`);
|
||||||
return;
|
return;
|
||||||
@ -185,7 +185,7 @@ class AuthCallback extends React.Component {
|
|||||||
Setting.goToLink(`${oAuthParams.redirectUri}${concatChar}code=${code}&state=${oAuthParams.state}`);
|
Setting.goToLink(`${oAuthParams.redirectUri}${concatChar}code=${code}&state=${oAuthParams.state}`);
|
||||||
// Setting.showMessage("success", `Authorization code: ${res.data}`);
|
// Setting.showMessage("success", `Authorization code: ${res.data}`);
|
||||||
} else if (responseType === "token" || responseType === "id_token") {
|
} else if (responseType === "token" || responseType === "id_token") {
|
||||||
if (res.data2) {
|
if (res.data3) {
|
||||||
sessionStorage.setItem("signinUrl", signinUrl);
|
sessionStorage.setItem("signinUrl", signinUrl);
|
||||||
Setting.goToLinkSoft(this, `/forget/${applicationName}`);
|
Setting.goToLinkSoft(this, `/forget/${applicationName}`);
|
||||||
return;
|
return;
|
||||||
@ -207,7 +207,7 @@ class AuthCallback extends React.Component {
|
|||||||
relayState: oAuthParams.relayState,
|
relayState: oAuthParams.relayState,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (res.data2.needUpdatePassword) {
|
if (res.data3) {
|
||||||
sessionStorage.setItem("signinUrl", signinUrl);
|
sessionStorage.setItem("signinUrl", signinUrl);
|
||||||
Setting.goToLinkSoft(this, `/forget/${applicationName}`);
|
Setting.goToLinkSoft(this, `/forget/${applicationName}`);
|
||||||
return;
|
return;
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Button, Col, Form, Input, Row, Select, Steps} from "antd";
|
import {Button, Col, Form, Input, Popover, Row, Select, Steps} from "antd";
|
||||||
import * as AuthBackend from "./AuthBackend";
|
import * as AuthBackend from "./AuthBackend";
|
||||||
import * as ApplicationBackend from "../backend/ApplicationBackend";
|
import * as ApplicationBackend from "../backend/ApplicationBackend";
|
||||||
import * as Util from "./Util";
|
import * as Util from "./Util";
|
||||||
@ -385,30 +385,48 @@ class ForgetPage extends React.Component {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<Form.Item
|
<Popover placement="right" content={this.state.passwordPopover} open={this.state.passwordPopoverOpen}>
|
||||||
name="newPassword"
|
<Form.Item
|
||||||
hidden={this.state.current !== 2}
|
name="newPassword"
|
||||||
rules={[
|
hidden={this.state.current !== 2}
|
||||||
{
|
rules={[
|
||||||
required: true,
|
{
|
||||||
validateTrigger: "onChange",
|
required: true,
|
||||||
validator: (rule, value) => {
|
validateTrigger: "onChange",
|
||||||
const errorMsg = PasswordChecker.checkPasswordComplexity(value, application.organizationObj.passwordOptions);
|
validator: (rule, value) => {
|
||||||
if (errorMsg === "") {
|
const errorMsg = PasswordChecker.checkPasswordComplexity(value, application.organizationObj.passwordOptions);
|
||||||
return Promise.resolve();
|
if (errorMsg === "") {
|
||||||
} else {
|
return Promise.resolve();
|
||||||
return Promise.reject(errorMsg);
|
} else {
|
||||||
}
|
return Promise.reject(errorMsg);
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
]}
|
||||||
]}
|
hasFeedback
|
||||||
hasFeedback
|
>
|
||||||
>
|
<Input.Password
|
||||||
<Input.Password
|
prefix={<LockOutlined />}
|
||||||
prefix={<LockOutlined />}
|
placeholder={i18next.t("general:Password")}
|
||||||
placeholder={i18next.t("general:Password")}
|
onChange={(e) => {
|
||||||
/>
|
this.setState({
|
||||||
</Form.Item>
|
passwordPopover: PasswordChecker.renderPasswordPopover(application.organizationObj.passwordOptions, e.target.value),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onFocus={() => {
|
||||||
|
this.setState({
|
||||||
|
passwordPopoverOpen: true,
|
||||||
|
passwordPopover: PasswordChecker.renderPasswordPopover(application.organizationObj.passwordOptions, this.form.current?.getFieldValue("newPassword") ?? ""),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onBlur={() => {
|
||||||
|
this.setState({
|
||||||
|
passwordPopoverOpen: false,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Popover>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="confirm"
|
name="confirm"
|
||||||
dependencies={["newPassword"]}
|
dependencies={["newPassword"]}
|
||||||
|
@ -134,6 +134,8 @@ class LoginPage extends React.Component {
|
|||||||
return CaptchaRule.Always;
|
return CaptchaRule.Always;
|
||||||
} else if (captchaProviderItems.some(providerItem => providerItem.rule === "Dynamic")) {
|
} else if (captchaProviderItems.some(providerItem => providerItem.rule === "Dynamic")) {
|
||||||
return CaptchaRule.Dynamic;
|
return CaptchaRule.Dynamic;
|
||||||
|
} else if (captchaProviderItems.some(providerItem => providerItem.rule === "Internet-Only")) {
|
||||||
|
return CaptchaRule.InternetOnly;
|
||||||
} else {
|
} else {
|
||||||
return CaptchaRule.Never;
|
return CaptchaRule.Never;
|
||||||
}
|
}
|
||||||
@ -443,6 +445,9 @@ class LoginPage extends React.Component {
|
|||||||
} else if (captchaRule === CaptchaRule.Dynamic) {
|
} else if (captchaRule === CaptchaRule.Dynamic) {
|
||||||
this.checkCaptchaStatus(values);
|
this.checkCaptchaStatus(values);
|
||||||
return;
|
return;
|
||||||
|
} else if (captchaRule === CaptchaRule.InternetOnly) {
|
||||||
|
this.checkCaptchaStatus(values);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.login(values);
|
this.login(values);
|
||||||
@ -491,9 +496,9 @@ class LoginPage extends React.Component {
|
|||||||
const responseType = values["type"];
|
const responseType = values["type"];
|
||||||
|
|
||||||
if (responseType === "login") {
|
if (responseType === "login") {
|
||||||
if (res.data2) {
|
if (res.data3) {
|
||||||
sessionStorage.setItem("signinUrl", window.location.pathname + window.location.search);
|
sessionStorage.setItem("signinUrl", window.location.pathname + window.location.search);
|
||||||
Setting.goToLink(this, `/forget/${this.state.applicationName}`);
|
Setting.goToLinkSoft(this, `/forget/${this.state.applicationName}`);
|
||||||
}
|
}
|
||||||
Setting.showMessage("success", i18next.t("application:Logged in successfully"));
|
Setting.showMessage("success", i18next.t("application:Logged in successfully"));
|
||||||
this.props.onLoginSuccess();
|
this.props.onLoginSuccess();
|
||||||
@ -505,9 +510,9 @@ class LoginPage extends React.Component {
|
|||||||
userCodeStatus: "success",
|
userCodeStatus: "success",
|
||||||
});
|
});
|
||||||
} else if (responseType === "token" || responseType === "id_token") {
|
} else if (responseType === "token" || responseType === "id_token") {
|
||||||
if (res.data2) {
|
if (res.data3) {
|
||||||
sessionStorage.setItem("signinUrl", window.location.pathname + window.location.search);
|
sessionStorage.setItem("signinUrl", window.location.pathname + window.location.search);
|
||||||
Setting.goToLink(this, `/forget/${this.state.applicationName}`);
|
Setting.goToLinkSoft(this, `/forget/${this.state.applicationName}`);
|
||||||
}
|
}
|
||||||
const amendatoryResponseType = responseType === "token" ? "access_token" : responseType;
|
const amendatoryResponseType = responseType === "token" ? "access_token" : responseType;
|
||||||
const accessToken = res.data;
|
const accessToken = res.data;
|
||||||
@ -517,9 +522,9 @@ class LoginPage extends React.Component {
|
|||||||
this.props.onLoginSuccess(window.location.href);
|
this.props.onLoginSuccess(window.location.href);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (res.data2.needUpdatePassword) {
|
if (res.data3) {
|
||||||
sessionStorage.setItem("signinUrl", window.location.pathname + window.location.search);
|
sessionStorage.setItem("signinUrl", window.location.pathname + window.location.search);
|
||||||
Setting.goToLink(this, `/forget/${this.state.applicationName}`);
|
Setting.goToLinkSoft(this, `/forget/${this.state.applicationName}`);
|
||||||
}
|
}
|
||||||
if (res.data2.method === "POST") {
|
if (res.data2.method === "POST") {
|
||||||
this.setState({
|
this.setState({
|
||||||
@ -961,9 +966,23 @@ class LoginPage extends React.Component {
|
|||||||
const captchaProviderItems = this.getCaptchaProviderItems(application);
|
const captchaProviderItems = this.getCaptchaProviderItems(application);
|
||||||
const alwaysProviderItems = captchaProviderItems.filter(providerItem => providerItem.rule === "Always");
|
const alwaysProviderItems = captchaProviderItems.filter(providerItem => providerItem.rule === "Always");
|
||||||
const dynamicProviderItems = captchaProviderItems.filter(providerItem => providerItem.rule === "Dynamic");
|
const dynamicProviderItems = captchaProviderItems.filter(providerItem => providerItem.rule === "Dynamic");
|
||||||
const provider = alwaysProviderItems.length > 0
|
const internetOnlyProviderItems = captchaProviderItems.filter(providerItem => providerItem.rule === "Internet-Only");
|
||||||
? alwaysProviderItems[0].provider
|
|
||||||
: dynamicProviderItems[0].provider;
|
// Select provider based on the active captcha rule, not fixed priority
|
||||||
|
const captchaRule = this.getCaptchaRule(this.getApplicationObj());
|
||||||
|
let provider = null;
|
||||||
|
|
||||||
|
if (captchaRule === CaptchaRule.Always && alwaysProviderItems.length > 0) {
|
||||||
|
provider = alwaysProviderItems[0].provider;
|
||||||
|
} else if (captchaRule === CaptchaRule.Dynamic && dynamicProviderItems.length > 0) {
|
||||||
|
provider = dynamicProviderItems[0].provider;
|
||||||
|
} else if (captchaRule === CaptchaRule.InternetOnly && internetOnlyProviderItems.length > 0) {
|
||||||
|
provider = internetOnlyProviderItems[0].provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return <CaptchaModal
|
return <CaptchaModal
|
||||||
owner={provider.owner}
|
owner={provider.owner}
|
||||||
|
@ -1,30 +1,30 @@
|
|||||||
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
//
|
//
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import * as Setting from "../Setting";
|
import * as Setting from "../Setting";
|
||||||
|
|
||||||
class OdicDiscoveryPage extends React.Component {
|
class OdicDiscoveryPage extends React.Component {
|
||||||
UNSAFE_componentWillMount() {
|
UNSAFE_componentWillMount() {
|
||||||
if (Setting.isLocalhost()) {
|
if (Setting.isLocalhost()) {
|
||||||
Setting.goToLink(`${Setting.ServerUrl}/.well-known/openid-configuration`);
|
Setting.goToLink(`${Setting.ServerUrl}/.well-known/openid-configuration`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default OdicDiscoveryPage;
|
export default OdicDiscoveryPage;
|
||||||
|
@ -278,7 +278,7 @@ const authInfo = {
|
|||||||
endpoint: "https://www.tiktok.com/auth/authorize/",
|
endpoint: "https://www.tiktok.com/auth/authorize/",
|
||||||
},
|
},
|
||||||
Tumblr: {
|
Tumblr: {
|
||||||
scope: "email",
|
scope: "basic",
|
||||||
endpoint: "https://www.tumblr.com/oauth2/authorize",
|
endpoint: "https://www.tumblr.com/oauth2/authorize",
|
||||||
},
|
},
|
||||||
Twitch: {
|
Twitch: {
|
||||||
|
@ -1,31 +1,31 @@
|
|||||||
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
//
|
//
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import {createButton} from "react-social-login-buttons";
|
import {createButton} from "react-social-login-buttons";
|
||||||
|
|
||||||
function Icon({width = 24, height = 24, color}) {
|
function Icon({width = 24, height = 24, color}) {
|
||||||
return <svg xmlns="http://www.w3.org/2000/svg" height="48" width="32" viewBox="-18.15 -35.9725 157.3 215.835"><path fill="#faab07" d="M60.503 142.237c-12.533 0-24.038-4.195-31.445-10.46-3.762 1.124-8.574 2.932-11.61 5.175-2.6 1.918-2.275 3.874-1.807 4.663 2.056 3.47 35.273 2.216 44.862 1.136zm0 0c12.535 0 24.039-4.195 31.447-10.46 3.76 1.124 8.573 2.932 11.61 5.175 2.598 1.918 2.274 3.874 1.805 4.663-2.056 3.47-35.272 2.216-44.862 1.136zm0 0" /><path d="M60.576 67.119c20.698-.14 37.286-4.147 42.907-5.683 1.34-.367 2.056-1.024 2.056-1.024.005-.189.085-3.37.085-5.01C105.624 27.768 92.58.001 60.5 0 28.42.001 15.375 27.769 15.375 55.401c0 1.642.08 4.822.086 5.01 0 0 .583.615 1.65.913 5.19 1.444 22.09 5.65 43.312 5.795zm56.245 23.02c-1.283-4.129-3.034-8.944-4.808-13.568 0 0-1.02-.126-1.537.023-15.913 4.623-35.202 7.57-49.9 7.392h-.153c-14.616.175-33.774-2.737-49.634-7.315-.606-.175-1.802-.1-1.802-.1-1.774 4.624-3.525 9.44-4.808 13.568-6.119 19.69-4.136 27.838-2.627 28.02 3.239.392 12.606-14.821 12.606-14.821 0 15.459 13.957 39.195 45.918 39.413h.848c31.96-.218 45.917-23.954 45.917-39.413 0 0 9.368 15.213 12.607 14.822 1.508-.183 3.491-8.332-2.627-28.021" /><path fill="#fff" d="M49.085 40.824c-4.352.197-8.07-4.76-8.304-11.063-.236-6.305 3.098-11.576 7.45-11.773 4.347-.195 8.064 4.76 8.3 11.065.238 6.306-3.097 11.577-7.446 11.771m31.133-11.063c-.233 6.302-3.951 11.26-8.303 11.063-4.35-.195-7.684-5.465-7.446-11.77.236-6.305 3.952-11.26 8.3-11.066 4.352.197 7.686 5.468 7.449 11.773" /><path fill="#faab07" d="M87.952 49.725C86.79 47.15 75.077 44.28 60.578 44.28h-.156c-14.5 0-26.212 2.87-27.375 5.446a.863.863 0 00-.085.367.88.88 0 00.16.496c.98 1.427 13.985 8.487 27.3 8.487h.156c13.314 0 26.319-7.058 27.299-8.487a.873.873 0 00.16-.498.856.856 0 00-.085-.365" /><path d="M54.434 29.854c.199 2.49-1.167 4.702-3.046 4.943-1.883.242-3.568-1.58-3.768-4.07-.197-2.492 1.167-4.704 3.043-4.944 1.886-.244 3.574 1.58 3.771 4.07m11.956.833c.385-.689 3.004-4.312 8.427-2.993 1.425.347 2.084.857 2.223 1.057.205.296.262.718.053 1.286-.412 1.126-1.263 1.095-1.734.875-.305-.142-4.082-2.66-7.562 1.097-.24.257-.668.346-1.073.04-.407-.308-.574-.93-.334-1.362" /><path fill="#fff" d="M60.576 83.08h-.153c-9.996.12-22.116-1.204-33.854-3.518-1.004 5.818-1.61 13.132-1.09 21.853 1.316 22.043 14.407 35.9 34.614 36.1h.82c20.208-.2 33.298-14.057 34.616-36.1.52-8.723-.087-16.035-1.092-21.854-11.739 2.315-23.862 3.64-33.86 3.518" /><path fill="#eb1923" d="M32.102 81.235v21.693s9.937 2.004 19.893.616V83.535c-6.307-.357-13.109-1.152-19.893-2.3" /><path fill="#eb1923" d="M105.539 60.412s-19.33 6.102-44.963 6.275h-.153c-25.591-.172-44.896-6.255-44.962-6.275L8.987 76.57c16.193 4.882 36.261 8.028 51.436 7.845h.153c15.175.183 35.242-2.963 51.437-7.845zm0 0" /></svg>;
|
return <svg xmlns="http://www.w3.org/2000/svg" height="48" width="32" viewBox="-18.15 -35.9725 157.3 215.835"><path fill="#faab07" d="M60.503 142.237c-12.533 0-24.038-4.195-31.445-10.46-3.762 1.124-8.574 2.932-11.61 5.175-2.6 1.918-2.275 3.874-1.807 4.663 2.056 3.47 35.273 2.216 44.862 1.136zm0 0c12.535 0 24.039-4.195 31.447-10.46 3.76 1.124 8.573 2.932 11.61 5.175 2.598 1.918 2.274 3.874 1.805 4.663-2.056 3.47-35.272 2.216-44.862 1.136zm0 0" /><path d="M60.576 67.119c20.698-.14 37.286-4.147 42.907-5.683 1.34-.367 2.056-1.024 2.056-1.024.005-.189.085-3.37.085-5.01C105.624 27.768 92.58.001 60.5 0 28.42.001 15.375 27.769 15.375 55.401c0 1.642.08 4.822.086 5.01 0 0 .583.615 1.65.913 5.19 1.444 22.09 5.65 43.312 5.795zm56.245 23.02c-1.283-4.129-3.034-8.944-4.808-13.568 0 0-1.02-.126-1.537.023-15.913 4.623-35.202 7.57-49.9 7.392h-.153c-14.616.175-33.774-2.737-49.634-7.315-.606-.175-1.802-.1-1.802-.1-1.774 4.624-3.525 9.44-4.808 13.568-6.119 19.69-4.136 27.838-2.627 28.02 3.239.392 12.606-14.821 12.606-14.821 0 15.459 13.957 39.195 45.918 39.413h.848c31.96-.218 45.917-23.954 45.917-39.413 0 0 9.368 15.213 12.607 14.822 1.508-.183 3.491-8.332-2.627-28.021" /><path fill="#fff" d="M49.085 40.824c-4.352.197-8.07-4.76-8.304-11.063-.236-6.305 3.098-11.576 7.45-11.773 4.347-.195 8.064 4.76 8.3 11.065.238 6.306-3.097 11.577-7.446 11.771m31.133-11.063c-.233 6.302-3.951 11.26-8.303 11.063-4.35-.195-7.684-5.465-7.446-11.77.236-6.305 3.952-11.26 8.3-11.066 4.352.197 7.686 5.468 7.449 11.773" /><path fill="#faab07" d="M87.952 49.725C86.79 47.15 75.077 44.28 60.578 44.28h-.156c-14.5 0-26.212 2.87-27.375 5.446a.863.863 0 00-.085.367.88.88 0 00.16.496c.98 1.427 13.985 8.487 27.3 8.487h.156c13.314 0 26.319-7.058 27.299-8.487a.873.873 0 00.16-.498.856.856 0 00-.085-.365" /><path d="M54.434 29.854c.199 2.49-1.167 4.702-3.046 4.943-1.883.242-3.568-1.58-3.768-4.07-.197-2.492 1.167-4.704 3.043-4.944 1.886-.244 3.574 1.58 3.771 4.07m11.956.833c.385-.689 3.004-4.312 8.427-2.993 1.425.347 2.084.857 2.223 1.057.205.296.262.718.053 1.286-.412 1.126-1.263 1.095-1.734.875-.305-.142-4.082-2.66-7.562 1.097-.24.257-.668.346-1.073.04-.407-.308-.574-.93-.334-1.362" /><path fill="#fff" d="M60.576 83.08h-.153c-9.996.12-22.116-1.204-33.854-3.518-1.004 5.818-1.61 13.132-1.09 21.853 1.316 22.043 14.407 35.9 34.614 36.1h.82c20.208-.2 33.298-14.057 34.616-36.1.52-8.723-.087-16.035-1.092-21.854-11.739 2.315-23.862 3.64-33.86 3.518" /><path fill="#eb1923" d="M32.102 81.235v21.693s9.937 2.004 19.893.616V83.535c-6.307-.357-13.109-1.152-19.893-2.3" /><path fill="#eb1923" d="M105.539 60.412s-19.33 6.102-44.963 6.275h-.153c-25.591-.172-44.896-6.255-44.962-6.275L8.987 76.57c16.193 4.882 36.261 8.028 51.436 7.845h.153c15.175.183 35.242-2.963 51.437-7.845zm0 0" /></svg>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
text: "Sign in with QQ",
|
text: "Sign in with QQ",
|
||||||
icon: Icon,
|
icon: Icon,
|
||||||
iconFormat: name => `fa fa-${name}`,
|
iconFormat: name => `fa fa-${name}`,
|
||||||
style: {background: "rgb(94,188,249)"},
|
style: {background: "rgb(94,188,249)"},
|
||||||
activeStyle: {background: "rgb(76,143,208)"},
|
activeStyle: {background: "rgb(76,143,208)"},
|
||||||
};
|
};
|
||||||
|
|
||||||
const QqLoginButton = createButton(config);
|
const QqLoginButton = createButton(config);
|
||||||
|
|
||||||
export default QqLoginButton;
|
export default QqLoginButton;
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Button, Form, Input, Radio, Result, Row, Select, message} from "antd";
|
import {Button, Form, Input, Popover, Radio, Result, Row, Select, message} from "antd";
|
||||||
import * as Setting from "../Setting";
|
import * as Setting from "../Setting";
|
||||||
import * as AuthBackend from "./AuthBackend";
|
import * as AuthBackend from "./AuthBackend";
|
||||||
import * as ProviderButton from "./ProviderButton";
|
import * as ProviderButton from "./ProviderButton";
|
||||||
@ -607,28 +607,45 @@ class SignupPage extends React.Component {
|
|||||||
}
|
}
|
||||||
} else if (signupItem.name === "Password") {
|
} else if (signupItem.name === "Password") {
|
||||||
return (
|
return (
|
||||||
<Form.Item
|
<Popover placement="right" content={this.state.passwordPopover} open={this.state.passwordPopoverOpen}>
|
||||||
name="password"
|
<Form.Item
|
||||||
className="signup-password"
|
name="password"
|
||||||
label={signupItem.label ? signupItem.label : i18next.t("general:Password")}
|
className="signup-password"
|
||||||
rules={[
|
label={signupItem.label ? signupItem.label : i18next.t("general:Password")}
|
||||||
{
|
rules={[
|
||||||
required: required,
|
{
|
||||||
validateTrigger: "onChange",
|
required: required,
|
||||||
validator: (rule, value) => {
|
validateTrigger: "onChange",
|
||||||
const errorMsg = PasswordChecker.checkPasswordComplexity(value, application.organizationObj.passwordOptions);
|
validator: (rule, value) => {
|
||||||
if (errorMsg === "") {
|
const errorMsg = PasswordChecker.checkPasswordComplexity(value, application.organizationObj.passwordOptions);
|
||||||
return Promise.resolve();
|
if (errorMsg === "") {
|
||||||
} else {
|
return Promise.resolve();
|
||||||
return Promise.reject(errorMsg);
|
} else {
|
||||||
}
|
return Promise.reject(errorMsg);
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
]}
|
||||||
]}
|
hasFeedback
|
||||||
hasFeedback
|
>
|
||||||
>
|
<Input.Password className="signup-password-input" placeholder={signupItem.placeholder} onChange={(e) => {
|
||||||
<Input.Password className="signup-password-input" placeholder={signupItem.placeholder} />
|
this.setState({
|
||||||
</Form.Item>
|
passwordPopover: PasswordChecker.renderPasswordPopover(application.organizationObj.passwordOptions, e.target.value),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onFocus={() => {
|
||||||
|
this.setState({
|
||||||
|
passwordPopoverOpen: true,
|
||||||
|
passwordPopover: PasswordChecker.renderPasswordPopover(application.organizationObj.passwordOptions, this.form.current?.getFieldValue("password") ?? ""),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onBlur={() => {
|
||||||
|
this.setState({
|
||||||
|
passwordPopoverOpen: false,
|
||||||
|
});
|
||||||
|
}} />
|
||||||
|
</Form.Item>
|
||||||
|
</Popover>
|
||||||
);
|
);
|
||||||
} else if (signupItem.name === "Confirm password") {
|
} else if (signupItem.name === "Confirm password") {
|
||||||
return (
|
return (
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {CopyOutlined, UserOutlined} from "@ant-design/icons";
|
import {CopyOutlined} from "@ant-design/icons";
|
||||||
import {Button, Col, Form, Input, QRCode, Space} from "antd";
|
import {Button, Col, Form, Input, QRCode, Space} from "antd";
|
||||||
import copy from "copy-to-clipboard";
|
import copy from "copy-to-clipboard";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
@ -47,11 +47,11 @@ export const MfaVerifyTotpForm = ({mfaProps, onFinish}) => {
|
|||||||
name="passcode"
|
name="passcode"
|
||||||
rules={[{required: true, message: "Please input your passcode"}]}
|
rules={[{required: true, message: "Please input your passcode"}]}
|
||||||
>
|
>
|
||||||
<Input
|
<Input.OTP
|
||||||
style={{marginTop: 24}}
|
style={{marginTop: 24}}
|
||||||
prefix={<UserOutlined />}
|
onChange={() => {
|
||||||
placeholder={i18next.t("mfa:Passcode")}
|
form.submit();
|
||||||
autoComplete="off"
|
}}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
|
@ -1,85 +1,85 @@
|
|||||||
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
//
|
//
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import * as Setting from "../Setting";
|
import * as Setting from "../Setting";
|
||||||
|
|
||||||
export function getResources(owner, user, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") {
|
export function getResources(owner, user, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") {
|
||||||
return fetch(`${Setting.ServerUrl}/api/get-resources?owner=${owner}&user=${user}&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, {
|
return fetch(`${Setting.ServerUrl}/api/get-resources?owner=${owner}&user=${user}&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
"Accept-Language": Setting.getAcceptLanguage(),
|
"Accept-Language": Setting.getAcceptLanguage(),
|
||||||
},
|
},
|
||||||
}).then(res => res.json());
|
}).then(res => res.json());
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getResource(owner, name) {
|
export function getResource(owner, name) {
|
||||||
return fetch(`${Setting.ServerUrl}/api/get-resource?id=${owner}/${encodeURIComponent(name)}`, {
|
return fetch(`${Setting.ServerUrl}/api/get-resource?id=${owner}/${encodeURIComponent(name)}`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
"Accept-Language": Setting.getAcceptLanguage(),
|
"Accept-Language": Setting.getAcceptLanguage(),
|
||||||
},
|
},
|
||||||
}).then(res => res.json());
|
}).then(res => res.json());
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateResource(owner, name, resource) {
|
export function updateResource(owner, name, resource) {
|
||||||
const newResource = Setting.deepCopy(resource);
|
const newResource = Setting.deepCopy(resource);
|
||||||
return fetch(`${Setting.ServerUrl}/api/update-resource?id=${owner}/${encodeURIComponent(name)}`, {
|
return fetch(`${Setting.ServerUrl}/api/update-resource?id=${owner}/${encodeURIComponent(name)}`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
body: JSON.stringify(newResource),
|
body: JSON.stringify(newResource),
|
||||||
headers: {
|
headers: {
|
||||||
"Accept-Language": Setting.getAcceptLanguage(),
|
"Accept-Language": Setting.getAcceptLanguage(),
|
||||||
},
|
},
|
||||||
}).then(res => res.json());
|
}).then(res => res.json());
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addResource(resource) {
|
export function addResource(resource) {
|
||||||
const newResource = Setting.deepCopy(resource);
|
const newResource = Setting.deepCopy(resource);
|
||||||
return fetch(`${Setting.ServerUrl}/api/add-resource`, {
|
return fetch(`${Setting.ServerUrl}/api/add-resource`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
body: JSON.stringify(newResource),
|
body: JSON.stringify(newResource),
|
||||||
headers: {
|
headers: {
|
||||||
"Accept-Language": Setting.getAcceptLanguage(),
|
"Accept-Language": Setting.getAcceptLanguage(),
|
||||||
},
|
},
|
||||||
}).then(res => res.json());
|
}).then(res => res.json());
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteResource(resource, provider = "") {
|
export function deleteResource(resource, provider = "") {
|
||||||
const newResource = Setting.deepCopy(resource);
|
const newResource = Setting.deepCopy(resource);
|
||||||
return fetch(`${Setting.ServerUrl}/api/delete-resource?provider=${provider}`, {
|
return fetch(`${Setting.ServerUrl}/api/delete-resource?provider=${provider}`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
body: JSON.stringify(newResource),
|
body: JSON.stringify(newResource),
|
||||||
headers: {
|
headers: {
|
||||||
"Accept-Language": Setting.getAcceptLanguage(),
|
"Accept-Language": Setting.getAcceptLanguage(),
|
||||||
},
|
},
|
||||||
}).then(res => res.json());
|
}).then(res => res.json());
|
||||||
}
|
}
|
||||||
|
|
||||||
export function uploadResource(owner, user, tag, parent, fullFilePath, file, provider = "") {
|
export function uploadResource(owner, user, tag, parent, fullFilePath, file, provider = "") {
|
||||||
const application = "app-built-in";
|
const application = "app-built-in";
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("file", file);
|
formData.append("file", file);
|
||||||
return fetch(`${Setting.ServerUrl}/api/upload-resource?owner=${owner}&user=${user}&application=${application}&tag=${tag}&parent=${parent}&fullFilePath=${encodeURIComponent(fullFilePath)}&provider=${provider}`, {
|
return fetch(`${Setting.ServerUrl}/api/upload-resource?owner=${owner}&user=${user}&application=${application}&tag=${tag}&parent=${parent}&fullFilePath=${encodeURIComponent(fullFilePath)}&provider=${provider}`, {
|
||||||
body: formData,
|
body: formData,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
"Accept-Language": Setting.getAcceptLanguage(),
|
"Accept-Language": Setting.getAcceptLanguage(),
|
||||||
},
|
},
|
||||||
}).then(res => res.json());
|
}).then(res => res.json());
|
||||||
}
|
}
|
||||||
|
@ -85,22 +85,31 @@ export const CaptchaWidget = (props) => {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "Aliyun Captcha": {
|
case "Aliyun Captcha": {
|
||||||
|
window.AliyunCaptchaConfig = {
|
||||||
|
region: "cn",
|
||||||
|
prefix: clientSecret2,
|
||||||
|
};
|
||||||
|
|
||||||
const AWSCTimer = setInterval(() => {
|
const AWSCTimer = setInterval(() => {
|
||||||
if (!window.AWSC) {
|
if (!window.initAliyunCaptcha) {
|
||||||
loadScript("https://g.alicdn.com/AWSC/AWSC/awsc.js");
|
loadScript("https://o.alicdn.com/captcha-frontend/aliyunCaptcha/AliyunCaptcha.js");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (window.AWSC) {
|
if (window.initAliyunCaptcha) {
|
||||||
if (clientSecret2 && clientSecret2 !== "***") {
|
if (clientSecret2 && clientSecret2 !== "***") {
|
||||||
window.AWSC.use(subType, function(state, module) {
|
window.initAliyunCaptcha({
|
||||||
module.init({
|
SceneId: clientId2,
|
||||||
appkey: clientSecret2,
|
mode: "embed",
|
||||||
scene: clientId2,
|
element: "#captcha",
|
||||||
renderTo: "captcha",
|
captchaVerifyCallback: (data) => {
|
||||||
success: function(data) {
|
onChange(data.toString());
|
||||||
onChange(`SessionId=${data.sessionId}&AccessKeyId=${siteKey}&Scene=${clientId2}&AppKey=${clientSecret2}&Token=${data.token}&Sig=${data.sig}&RemoteIp=192.168.0.1`);
|
},
|
||||||
},
|
slideStyle: {
|
||||||
});
|
width: 320,
|
||||||
|
height: 40,
|
||||||
|
},
|
||||||
|
language: "cn",
|
||||||
|
immediate: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
clearInterval(AWSCTimer);
|
clearInterval(AWSCTimer);
|
||||||
|
@ -1,229 +1,229 @@
|
|||||||
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
//
|
//
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Button, Col, Row} from "antd";
|
import {Button, Col, Row} from "antd";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import * as UserBackend from "../backend/UserBackend";
|
import * as UserBackend from "../backend/UserBackend";
|
||||||
import * as Setting from "../Setting";
|
import * as Setting from "../Setting";
|
||||||
import * as Provider from "../auth/Provider";
|
import * as Provider from "../auth/Provider";
|
||||||
import * as AuthBackend from "../auth/AuthBackend";
|
import * as AuthBackend from "../auth/AuthBackend";
|
||||||
import {goToWeb3Url} from "../auth/ProviderButton";
|
import {goToWeb3Url} from "../auth/ProviderButton";
|
||||||
import AccountAvatar from "../account/AccountAvatar";
|
import AccountAvatar from "../account/AccountAvatar";
|
||||||
import {WechatOfficialAccountModal} from "../auth/Util";
|
import {WechatOfficialAccountModal} from "../auth/Util";
|
||||||
|
|
||||||
class OAuthWidget extends React.Component {
|
class OAuthWidget extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
classes: props,
|
classes: props,
|
||||||
addressOptions: [],
|
addressOptions: [],
|
||||||
affiliationOptions: [],
|
affiliationOptions: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
UNSAFE_componentWillMount() {
|
UNSAFE_componentWillMount() {
|
||||||
this.getAddressOptions(this.props.application);
|
this.getAddressOptions(this.props.application);
|
||||||
this.getAffiliationOptions(this.props.application, this.props.user);
|
this.getAffiliationOptions(this.props.application, this.props.user);
|
||||||
}
|
}
|
||||||
|
|
||||||
getAddressOptions(application) {
|
getAddressOptions(application) {
|
||||||
if (application.affiliationUrl === "") {
|
if (application.affiliationUrl === "") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const addressUrl = application.affiliationUrl.split("|")[0];
|
const addressUrl = application.affiliationUrl.split("|")[0];
|
||||||
UserBackend.getAddressOptions(addressUrl)
|
UserBackend.getAddressOptions(addressUrl)
|
||||||
.then((addressOptions) => {
|
.then((addressOptions) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
addressOptions: addressOptions,
|
addressOptions: addressOptions,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getAffiliationOptions(application, user) {
|
getAffiliationOptions(application, user) {
|
||||||
if (application.affiliationUrl === "") {
|
if (application.affiliationUrl === "") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const affiliationUrl = application.affiliationUrl.split("|")[1];
|
const affiliationUrl = application.affiliationUrl.split("|")[1];
|
||||||
const code = user.address[user.address.length - 1];
|
const code = user.address[user.address.length - 1];
|
||||||
UserBackend.getAffiliationOptions(affiliationUrl, code)
|
UserBackend.getAffiliationOptions(affiliationUrl, code)
|
||||||
.then((affiliationOptions) => {
|
.then((affiliationOptions) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
affiliationOptions: affiliationOptions,
|
affiliationOptions: affiliationOptions,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateUserField(key, value) {
|
updateUserField(key, value) {
|
||||||
this.props.onUpdateUserField(key, value);
|
this.props.onUpdateUserField(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
unlinked() {
|
unlinked() {
|
||||||
this.props.onUnlinked();
|
this.props.onUnlinked();
|
||||||
}
|
}
|
||||||
|
|
||||||
getProviderLink(user, provider) {
|
getProviderLink(user, provider) {
|
||||||
if (provider.type === "GitHub") {
|
if (provider.type === "GitHub") {
|
||||||
return `https://github.com/${this.getUserProperty(user, provider.type, "username")}`;
|
return `https://github.com/${this.getUserProperty(user, provider.type, "username")}`;
|
||||||
} else if (provider.type === "Google") {
|
} else if (provider.type === "Google") {
|
||||||
return "https://mail.google.com";
|
return "https://mail.google.com";
|
||||||
} else {
|
} else {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getUserProperty(user, providerType, propertyName) {
|
getUserProperty(user, providerType, propertyName) {
|
||||||
const key = `oauth_${providerType}_${propertyName}`;
|
const key = `oauth_${providerType}_${propertyName}`;
|
||||||
if (user.properties === null) {return "";}
|
if (user.properties === null) {return "";}
|
||||||
return user.properties[key];
|
return user.properties[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
unlinkUser(providerType, linkedValue) {
|
unlinkUser(providerType, linkedValue) {
|
||||||
const body = {
|
const body = {
|
||||||
providerType: providerType,
|
providerType: providerType,
|
||||||
// should add the unlink user's info, cause the user may not be logged in, but a admin want to unlink the user.
|
// should add the unlink user's info, cause the user may not be logged in, but a admin want to unlink the user.
|
||||||
user: this.props.user,
|
user: this.props.user,
|
||||||
};
|
};
|
||||||
if (providerType === "MetaMask" || providerType === "Web3Onboard") {
|
if (providerType === "MetaMask" || providerType === "Web3Onboard") {
|
||||||
import("../auth/Web3Auth")
|
import("../auth/Web3Auth")
|
||||||
.then(module => {
|
.then(module => {
|
||||||
const delWeb3AuthToken = module.delWeb3AuthToken;
|
const delWeb3AuthToken = module.delWeb3AuthToken;
|
||||||
delWeb3AuthToken(linkedValue);
|
delWeb3AuthToken(linkedValue);
|
||||||
AuthBackend.unlink(body)
|
AuthBackend.unlink(body)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", "Unlinked successfully");
|
Setting.showMessage("success", "Unlinked successfully");
|
||||||
|
|
||||||
this.unlinked();
|
this.unlinked();
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `Failed to unlink: ${res.msg}`);
|
Setting.showMessage("error", `Failed to unlink: ${res.msg}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
AuthBackend.unlink(body)
|
AuthBackend.unlink(body)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", "Unlinked successfully");
|
Setting.showMessage("success", "Unlinked successfully");
|
||||||
|
|
||||||
this.unlinked();
|
this.unlinked();
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `Failed to unlink: ${res.msg}`);
|
Setting.showMessage("error", `Failed to unlink: ${res.msg}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
renderIdp(user, application, providerItem) {
|
renderIdp(user, application, providerItem) {
|
||||||
const provider = providerItem.provider;
|
const provider = providerItem.provider;
|
||||||
const linkedValue = user[provider.type.toLowerCase()];
|
const linkedValue = user[provider.type.toLowerCase()];
|
||||||
const profileUrl = this.getProviderLink(user, provider);
|
const profileUrl = this.getProviderLink(user, provider);
|
||||||
const id = this.getUserProperty(user, provider.type, "id");
|
const id = this.getUserProperty(user, provider.type, "id");
|
||||||
const username = this.getUserProperty(user, provider.type, "username");
|
const username = this.getUserProperty(user, provider.type, "username");
|
||||||
const displayName = this.getUserProperty(user, provider.type, "displayName");
|
const displayName = this.getUserProperty(user, provider.type, "displayName");
|
||||||
const email = this.getUserProperty(user, provider.type, "email");
|
const email = this.getUserProperty(user, provider.type, "email");
|
||||||
let avatarUrl = this.getUserProperty(user, provider.type, "avatarUrl");
|
let avatarUrl = this.getUserProperty(user, provider.type, "avatarUrl");
|
||||||
// the account user
|
// the account user
|
||||||
const account = this.props.account;
|
const account = this.props.account;
|
||||||
|
|
||||||
if (avatarUrl === "" || avatarUrl === undefined) {
|
if (avatarUrl === "" || avatarUrl === undefined) {
|
||||||
avatarUrl = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAQAAACROWYpAAAAHElEQVR42mNkoAAwjmoe1TyqeVTzqOZRzcNZMwB18wAfEFQkPQAAAABJRU5ErkJggg==";
|
avatarUrl = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAQAAACROWYpAAAAHElEQVR42mNkoAAwjmoe1TyqeVTzqOZRzcNZMwB18wAfEFQkPQAAAABJRU5ErkJggg==";
|
||||||
}
|
}
|
||||||
|
|
||||||
let name = (username === undefined) ? displayName : `${displayName} (${username})`;
|
let name = (username === undefined) ? displayName : `${displayName} (${username})`;
|
||||||
if (name === undefined) {
|
if (name === undefined) {
|
||||||
if (id !== undefined) {
|
if (id !== undefined) {
|
||||||
name = id;
|
name = id;
|
||||||
} else if (email !== undefined) {
|
} else if (email !== undefined) {
|
||||||
name = email;
|
name = email;
|
||||||
} else {
|
} else {
|
||||||
name = linkedValue;
|
name = linkedValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let linkButtonWidth = "110px";
|
let linkButtonWidth = "110px";
|
||||||
if (Setting.getLanguage() === "id") {
|
if (Setting.getLanguage() === "id") {
|
||||||
linkButtonWidth = "160px";
|
linkButtonWidth = "160px";
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row key={provider.name} style={{marginTop: "20px"}} >
|
<Row key={provider.name} style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={this.props.labelSpan}>
|
<Col style={{marginTop: "5px"}} span={this.props.labelSpan}>
|
||||||
{
|
{
|
||||||
Setting.getProviderLogo(provider)
|
Setting.getProviderLogo(provider)
|
||||||
}
|
}
|
||||||
<span style={{marginLeft: "5px"}}>
|
<span style={{marginLeft: "5px"}}>
|
||||||
{
|
{
|
||||||
`${provider.type}:`
|
`${provider.type}:`
|
||||||
}
|
}
|
||||||
</span>
|
</span>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={24 - this.props.labelSpan} >
|
<Col span={24 - this.props.labelSpan} >
|
||||||
<AccountAvatar style={{marginRight: "10px"}} size={30} src={avatarUrl} alt={name} referrerPolicy="no-referrer" />
|
<AccountAvatar style={{marginRight: "10px"}} size={30} src={avatarUrl} alt={name} referrerPolicy="no-referrer" />
|
||||||
<span style={{
|
<span style={{
|
||||||
width: this.props.labelSpan === 3 ? "300px" : "200px",
|
width: this.props.labelSpan === 3 ? "300px" : "200px",
|
||||||
display: (Setting.isMobile()) ? "inline" : "inline-block",
|
display: (Setting.isMobile()) ? "inline" : "inline-block",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
textOverflow: "ellipsis",
|
textOverflow: "ellipsis",
|
||||||
}} title={name}>
|
}} title={name}>
|
||||||
{
|
{
|
||||||
linkedValue === "" ? (
|
linkedValue === "" ? (
|
||||||
`(${i18next.t("general:empty")})`
|
`(${i18next.t("general:empty")})`
|
||||||
) : (
|
) : (
|
||||||
profileUrl === "" ? name : (
|
profileUrl === "" ? name : (
|
||||||
<a target="_blank" rel="noreferrer" href={profileUrl}>
|
<a target="_blank" rel="noreferrer" href={profileUrl}>
|
||||||
{
|
{
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
</a>
|
</a>
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</span>
|
</span>
|
||||||
{
|
{
|
||||||
linkedValue === "" ? (
|
linkedValue === "" ? (
|
||||||
provider.category === "Web3" ? (
|
provider.category === "Web3" ? (
|
||||||
<Button style={{marginLeft: "20px", width: linkButtonWidth}} type="primary" disabled={user.id !== account.id} onClick={() => goToWeb3Url(application, provider, "link")}>{i18next.t("user:Link")}</Button>
|
<Button style={{marginLeft: "20px", width: linkButtonWidth}} type="primary" disabled={user.id !== account.id} onClick={() => goToWeb3Url(application, provider, "link")}>{i18next.t("user:Link")}</Button>
|
||||||
) : (
|
) : (
|
||||||
provider.type === "WeChat" && provider.clientId2 !== "" && provider.clientSecret2 !== "" && provider.disableSsl === true && !navigator.userAgent.includes("MicroMessenger") ? (
|
provider.type === "WeChat" && provider.clientId2 !== "" && provider.clientSecret2 !== "" && provider.disableSsl === true && !navigator.userAgent.includes("MicroMessenger") ? (
|
||||||
<a key={provider.displayName}>
|
<a key={provider.displayName}>
|
||||||
<Button style={{marginLeft: "20px", width: linkButtonWidth}} type="primary" disabled={user.id !== account.id} onClick={
|
<Button style={{marginLeft: "20px", width: linkButtonWidth}} type="primary" disabled={user.id !== account.id} onClick={
|
||||||
() => {
|
() => {
|
||||||
WechatOfficialAccountModal(application, provider, "link");
|
WechatOfficialAccountModal(application, provider, "link");
|
||||||
}
|
}
|
||||||
}>{i18next.t("user:Link")}</Button>
|
}>{i18next.t("user:Link")}</Button>
|
||||||
</a>
|
</a>
|
||||||
) : (
|
) : (
|
||||||
<a key={provider.displayName} href={user.id !== account.id ? null : Provider.getAuthUrl(application, provider, "link")}>
|
<a key={provider.displayName} href={user.id !== account.id ? null : Provider.getAuthUrl(application, provider, "link")}>
|
||||||
<Button style={{marginLeft: "20px", width: linkButtonWidth}} type="primary" disabled={user.id !== account.id}>{i18next.t("user:Link")}</Button>
|
<Button style={{marginLeft: "20px", width: linkButtonWidth}} type="primary" disabled={user.id !== account.id}>{i18next.t("user:Link")}</Button>
|
||||||
</a>
|
</a>
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<Button disabled={!providerItem.canUnlink && !Setting.isAdminUser(account)} style={{marginLeft: "20px", width: linkButtonWidth}} onClick={() => this.unlinkUser(provider.type, linkedValue)}>{i18next.t("user:Unlink")}</Button>
|
<Button disabled={!providerItem.canUnlink && !Setting.isAdminUser(account)} style={{marginLeft: "20px", width: linkButtonWidth}} onClick={() => this.unlinkUser(provider.type, linkedValue)}>{i18next.t("user:Unlink")}</Button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return this.renderIdp(this.props.user, this.props.application, this.props.providerItem);
|
return this.renderIdp(this.props.user, this.props.application, this.props.providerItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default OAuthWidget;
|
export default OAuthWidget;
|
||||||
|
@ -13,6 +13,8 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import React from "react";
|
||||||
|
import {CheckCircleTwoTone, CloseCircleTwoTone} from "@ant-design/icons";
|
||||||
|
|
||||||
function isValidOption_AtLeast6(password) {
|
function isValidOption_AtLeast6(password) {
|
||||||
if (password.length < 6) {
|
if (password.length < 6) {
|
||||||
@ -52,6 +54,33 @@ function isValidOption_NoRepeat(password) {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const checkers = {
|
||||||
|
AtLeast6: isValidOption_AtLeast6,
|
||||||
|
AtLeast8: isValidOption_AtLeast8,
|
||||||
|
Aa123: isValidOption_Aa123,
|
||||||
|
SpecialChar: isValidOption_SpecialChar,
|
||||||
|
NoRepeat: isValidOption_NoRepeat,
|
||||||
|
};
|
||||||
|
|
||||||
|
function getOptionDescription(option, password) {
|
||||||
|
switch (option) {
|
||||||
|
case "AtLeast6": return i18next.t("user:The password must have at least 6 characters");
|
||||||
|
case "AtLeast8": return i18next.t("user:The password must have at least 8 characters");
|
||||||
|
case "Aa123": return i18next.t("user:The password must contain at least one uppercase letter, one lowercase letter and one digit");
|
||||||
|
case "SpecialChar": return i18next.t("user:The password must contain at least one special character");
|
||||||
|
case "NoRepeat": return i18next.t("user:The password must not contain any repeated characters");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function renderPasswordPopover(options, password) {
|
||||||
|
return <div style={{width: 240}} >
|
||||||
|
{options.map((option, idx) => {
|
||||||
|
return <div key={idx}>{checkers[option](password) === "" ? <CheckCircleTwoTone twoToneColor={"#52c41a"} /> :
|
||||||
|
<CloseCircleTwoTone twoToneColor={"#ff4d4f"} />} {getOptionDescription(option, password)}</div>;
|
||||||
|
})}
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
export function checkPasswordComplexity(password, options) {
|
export function checkPasswordComplexity(password, options) {
|
||||||
if (password.length === 0) {
|
if (password.length === 0) {
|
||||||
return i18next.t("login:Please input your password!");
|
return i18next.t("login:Please input your password!");
|
||||||
@ -61,14 +90,6 @@ export function checkPasswordComplexity(password, options) {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkers = {
|
|
||||||
AtLeast6: isValidOption_AtLeast6,
|
|
||||||
AtLeast8: isValidOption_AtLeast8,
|
|
||||||
Aa123: isValidOption_Aa123,
|
|
||||||
SpecialChar: isValidOption_SpecialChar,
|
|
||||||
NoRepeat: isValidOption_NoRepeat,
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const option of options) {
|
for (const option of options) {
|
||||||
const checkerFunc = checkers[option];
|
const checkerFunc = checkers[option];
|
||||||
if (checkerFunc) {
|
if (checkerFunc) {
|
||||||
|
@ -44,6 +44,12 @@ export const CaptchaModal = (props) => {
|
|||||||
}
|
}
|
||||||
}, [visible]);
|
}, [visible]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (captchaToken !== "" && captchaType !== "Default") {
|
||||||
|
handleOk();
|
||||||
|
}
|
||||||
|
}, [captchaToken]);
|
||||||
|
|
||||||
const handleOk = () => {
|
const handleOk = () => {
|
||||||
onOk?.(captchaType, captchaToken, clientSecret);
|
onOk?.(captchaType, captchaToken, clientSecret);
|
||||||
};
|
};
|
||||||
@ -138,19 +144,18 @@ export const CaptchaModal = (props) => {
|
|||||||
if (!regex.test(captchaToken)) {
|
if (!regex.test(captchaToken)) {
|
||||||
isOkDisabled = true;
|
isOkDisabled = true;
|
||||||
}
|
}
|
||||||
} else if (captchaToken === "") {
|
return [
|
||||||
isOkDisabled = true;
|
null,
|
||||||
|
<Button key="ok" disabled={isOkDisabled} type="primary" onClick={handleOk}>{i18next.t("general:OK")}</Button>,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return null;
|
||||||
<Button key="cancel" onClick={handleCancel}>{i18next.t("general:Cancel")}</Button>,
|
|
||||||
<Button key="ok" disabled={isOkDisabled} type="primary" onClick={handleOk}>{i18next.t("general:OK")}</Button>,
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
closable={false}
|
closable={true}
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
destroyOnClose={true}
|
destroyOnClose={true}
|
||||||
title={i18next.t("general:Captcha")}
|
title={i18next.t("general:Captcha")}
|
||||||
@ -160,6 +165,7 @@ export const CaptchaModal = (props) => {
|
|||||||
width={350}
|
width={350}
|
||||||
footer={renderFooter()}
|
footer={renderFooter()}
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
|
afterClose={handleCancel}
|
||||||
onOk={handleOk}
|
onOk={handleOk}
|
||||||
>
|
>
|
||||||
<div style={{marginTop: "20px", marginBottom: "50px"}}>
|
<div style={{marginTop: "20px", marginBottom: "50px"}}>
|
||||||
@ -175,4 +181,5 @@ export const CaptchaRule = {
|
|||||||
Always: "Always",
|
Always: "Always",
|
||||||
Never: "Never",
|
Never: "Never",
|
||||||
Dynamic: "Dynamic",
|
Dynamic: "Dynamic",
|
||||||
|
InternetOnly: "Internet-Only",
|
||||||
};
|
};
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import {Button, Col, Input, Modal, Row} from "antd";
|
import {Button, Col, Input, Modal, Popover, Row} from "antd";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import * as UserBackend from "../../backend/UserBackend";
|
import * as UserBackend from "../../backend/UserBackend";
|
||||||
@ -35,6 +35,8 @@ export const PasswordModal = (props) => {
|
|||||||
const [rePasswordValid, setRePasswordValid] = React.useState(false);
|
const [rePasswordValid, setRePasswordValid] = React.useState(false);
|
||||||
const [newPasswordErrorMessage, setNewPasswordErrorMessage] = React.useState("");
|
const [newPasswordErrorMessage, setNewPasswordErrorMessage] = React.useState("");
|
||||||
const [rePasswordErrorMessage, setRePasswordErrorMessage] = React.useState("");
|
const [rePasswordErrorMessage, setRePasswordErrorMessage] = React.useState("");
|
||||||
|
const [passwordPopoverOpen, setPasswordPopoverOpen] = React.useState(false);
|
||||||
|
const [passwordPopover, setPasswordPopover] = React.useState();
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (organization) {
|
if (organization) {
|
||||||
@ -130,12 +132,26 @@ export const PasswordModal = (props) => {
|
|||||||
</Row>
|
</Row>
|
||||||
) : null}
|
) : null}
|
||||||
<Row style={{width: "100%", marginBottom: "20px"}}>
|
<Row style={{width: "100%", marginBottom: "20px"}}>
|
||||||
<Input.Password
|
<Popover placement="right" content={passwordPopover} open={passwordPopoverOpen}>
|
||||||
addonBefore={i18next.t("user:New Password")}
|
<Input.Password
|
||||||
placeholder={i18next.t("user:input password")}
|
addonBefore={i18next.t("user:New Password")}
|
||||||
onChange={(e) => {handleNewPassword(e.target.value);}}
|
placeholder={i18next.t("user:input password")}
|
||||||
status={(!newPasswordValid && newPasswordErrorMessage) ? "error" : undefined}
|
onChange={(e) => {
|
||||||
/>
|
handleNewPassword(e.target.value);
|
||||||
|
setPasswordPopoverOpen(true);
|
||||||
|
setPasswordPopover(PasswordChecker.renderPasswordPopover(passwordOptions, e.target.value));
|
||||||
|
|
||||||
|
}}
|
||||||
|
onFocus={() => {
|
||||||
|
setPasswordPopoverOpen(true);
|
||||||
|
setPasswordPopover(PasswordChecker.renderPasswordPopover(passwordOptions, newPassword));
|
||||||
|
}}
|
||||||
|
onBlur={() => {
|
||||||
|
setPasswordPopoverOpen(false);
|
||||||
|
}}
|
||||||
|
status={(!newPasswordValid && newPasswordErrorMessage) ? "error" : undefined}
|
||||||
|
/>
|
||||||
|
</Popover>
|
||||||
</Row>
|
</Row>
|
||||||
{!newPasswordValid && newPasswordErrorMessage && <div style={{color: "red", marginTop: "-20px"}}>{newPasswordErrorMessage}</div>}
|
{!newPasswordValid && newPasswordErrorMessage && <div style={{color: "red", marginTop: "-20px"}}>{newPasswordErrorMessage}</div>}
|
||||||
<Row style={{width: "100%", marginBottom: "20px"}}>
|
<Row style={{width: "100%", marginBottom: "20px"}}>
|
||||||
|
@ -76,6 +76,8 @@
|
|||||||
"Header HTML - Tooltip": "Custom the head tag of your application entry page",
|
"Header HTML - Tooltip": "Custom the head tag of your application entry page",
|
||||||
"Incremental": "Incremental",
|
"Incremental": "Incremental",
|
||||||
"Input": "Input",
|
"Input": "Input",
|
||||||
|
"Internet-Only": "Internet-Only",
|
||||||
|
"Invalid characters in application name": "Invalid characters in application name",
|
||||||
"Invitation code": "Invitation code",
|
"Invitation code": "Invitation code",
|
||||||
"Left": "Left",
|
"Left": "Left",
|
||||||
"Logged in successfully": "Logged in successfully",
|
"Logged in successfully": "Logged in successfully",
|
||||||
@ -454,6 +456,7 @@
|
|||||||
"Parent group - Tooltip": "Parent group - Tooltip",
|
"Parent group - Tooltip": "Parent group - Tooltip",
|
||||||
"Physical": "Physical",
|
"Physical": "Physical",
|
||||||
"Show all": "Show all",
|
"Show all": "Show all",
|
||||||
|
"Upload (.xlsx)": "Upload (.xlsx)",
|
||||||
"Virtual": "Virtual",
|
"Virtual": "Virtual",
|
||||||
"You need to delete all subgroups first. You can view the subgroups in the left group tree of the [Organizations] -\u003e [Groups] page": "You need to delete all subgroups first. You can view the subgroups in the left group tree of the [Organizations] -\u003e [Groups] page"
|
"You need to delete all subgroups first. You can view the subgroups in the left group tree of the [Organizations] -\u003e [Groups] page": "You need to delete all subgroups first. You can view the subgroups in the left group tree of the [Organizations] -\u003e [Groups] page"
|
||||||
},
|
},
|
||||||
@ -778,6 +781,8 @@
|
|||||||
"Sold": "Sold",
|
"Sold": "Sold",
|
||||||
"Sold - Tooltip": "Quantity sold",
|
"Sold - Tooltip": "Quantity sold",
|
||||||
"Stripe": "Stripe",
|
"Stripe": "Stripe",
|
||||||
|
"Success URL": "Success URL",
|
||||||
|
"Success URL - Tooltip": "URL to return to after purchase",
|
||||||
"Tag - Tooltip": "Tag of product",
|
"Tag - Tooltip": "Tag of product",
|
||||||
"Test buy page..": "Test buy page..",
|
"Test buy page..": "Test buy page..",
|
||||||
"There is no payment channel for this product.": "There is no payment channel for this product.",
|
"There is no payment channel for this product.": "There is no payment channel for this product.",
|
||||||
@ -860,7 +865,6 @@
|
|||||||
"Host - Tooltip": "Name of host",
|
"Host - Tooltip": "Name of host",
|
||||||
"IdP": "IdP",
|
"IdP": "IdP",
|
||||||
"IdP certificate": "IdP certificate",
|
"IdP certificate": "IdP certificate",
|
||||||
"Intelligent Validation": "Intelligent Validation",
|
|
||||||
"Internal": "Internal",
|
"Internal": "Internal",
|
||||||
"Issuer URL": "Issuer URL",
|
"Issuer URL": "Issuer URL",
|
||||||
"Issuer URL - Tooltip": "Issuer URL",
|
"Issuer URL - Tooltip": "Issuer URL",
|
||||||
@ -946,7 +950,6 @@
|
|||||||
"Silent": "Silent",
|
"Silent": "Silent",
|
||||||
"Site key": "Site key",
|
"Site key": "Site key",
|
||||||
"Site key - Tooltip": "Site key",
|
"Site key - Tooltip": "Site key",
|
||||||
"Sliding Validation": "Sliding Validation",
|
|
||||||
"Sub type": "Sub type",
|
"Sub type": "Sub type",
|
||||||
"Sub type - Tooltip": "Sub type",
|
"Sub type - Tooltip": "Sub type",
|
||||||
"Subject": "Subject",
|
"Subject": "Subject",
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user